Merge "Add SpaLogger."
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 2682dd7..442c130 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -36,9 +36,11 @@
 
     /**
      * Cancel the jobs for a given uid (e.g. when app data is cleared)
+     *
+     * @param includeProxiedJobs Include jobs scheduled for this UID by other apps
      */
-    void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, int debugReasonCode,
-            String debugReason);
+    void cancelJobsForUid(int uid, boolean includeProxiedJobs,
+            @JobParameters.StopReason int reason, int debugReasonCode, String debugReason);
 
     /**
      * These are for activity manager to communicate to use what is currently performing backups.
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 5860384..20bca35 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -34,6 +34,7 @@
 import android.content.pm.UserInfo;
 import android.os.BatteryStats;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -181,6 +182,7 @@
     private final JobSchedulerService mService;
     private final Context mContext;
     private final Handler mHandler;
+    private final Injector mInjector;
 
     private PowerManager mPowerManager;
 
@@ -378,9 +380,15 @@
     }
 
     JobConcurrencyManager(JobSchedulerService service) {
+        this(service, new Injector());
+    }
+
+    @VisibleForTesting
+    JobConcurrencyManager(JobSchedulerService service, Injector injector) {
         mService = service;
         mLock = mService.mLock;
         mContext = service.getTestableContext();
+        mInjector = injector;
 
         mHandler = JobSchedulerBackgroundThread.getHandler();
 
@@ -414,7 +422,7 @@
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
         for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) {
             mIdleContexts.add(
-                    new JobServiceContext(mService, this, batteryStats,
+                    mInjector.createJobServiceContext(mService, this, batteryStats,
                             mService.mJobPackageTracker, mContext.getMainLooper()));
         }
     }
@@ -657,15 +665,40 @@
             return;
         }
 
+        prepareForAssignmentDeterminationLocked(
+                mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+
+        if (DEBUG) {
+            Slog.d(TAG, printAssignments("running jobs initial",
+                    mRecycledStoppable, mRecycledPreferredUidOnly));
+        }
+
+        determineAssignmentsLocked(
+                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+
+        if (DEBUG) {
+            Slog.d(TAG, printAssignments("running jobs final",
+                    mRecycledStoppable, mRecycledPreferredUidOnly, mRecycledChanged));
+
+            Slog.d(TAG, "work count results: " + mWorkCountTracker);
+        }
+
+        carryOutAssignmentChangesLocked(mRecycledChanged);
+
+        cleanUpAfterAssignmentChangesLocked(
+                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+
+        noteConcurrency();
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
+            final List<ContextAssignment> preferredUidOnly,
+            final List<ContextAssignment> stoppable) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
         final List<JobServiceContext> activeServices = mActiveServices;
 
-        // To avoid GC churn, we recycle the arrays.
-        final ArraySet<ContextAssignment> changed = mRecycledChanged;
-        final ArraySet<ContextAssignment> idle = mRecycledIdle;
-        final ArrayList<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly;
-        final ArrayList<ContextAssignment> stoppable = mRecycledStoppable;
-
         updateCounterConfigLocked();
         // Reset everything since we'll re-evaluate the current state.
         mWorkCountTracker.resetCounts();
@@ -719,15 +752,21 @@
             assignment.context = jsc;
             idle.add(assignment);
         }
-        if (DEBUG) {
-            Slog.d(TAG, printAssignments("running jobs initial", stoppable, preferredUidOnly));
-        }
 
         mWorkCountTracker.onCountDone();
+    }
 
-        JobStatus nextPending;
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
+            final ArraySet<ContextAssignment> idle,
+            final List<ContextAssignment> preferredUidOnly,
+            final List<ContextAssignment> stoppable) {
+        final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
+        final List<JobServiceContext> activeServices = mActiveServices;
         pendingJobQueue.resetIterator();
-        int projectedRunningCount = numRunningJobs;
+        JobStatus nextPending;
+        int projectedRunningCount = activeServices.size();
         while ((nextPending = pendingJobQueue.next()) != null) {
             if (mRunningJobs.contains(nextPending)) {
                 // Should never happen.
@@ -895,13 +934,10 @@
                         packageStats);
             }
         }
-        if (DEBUG) {
-            Slog.d(TAG, printAssignments("running jobs final",
-                    stoppable, preferredUidOnly, changed));
+    }
 
-            Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
-        }
-
+    @GuardedBy("mLock")
+    private void carryOutAssignmentChangesLocked(final ArraySet<ContextAssignment> changed) {
         for (int c = changed.size() - 1; c >= 0; --c) {
             final ContextAssignment assignment = changed.valueAt(c);
             final JobStatus js = assignment.context.getRunningJobLocked();
@@ -925,6 +961,13 @@
             assignment.clear();
             mContextAssignmentPool.release(assignment);
         }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
+            final ArraySet<ContextAssignment> idle,
+            final List<ContextAssignment> preferredUidOnly,
+            final List<ContextAssignment> stoppable) {
         for (int s = stoppable.size() - 1; s >= 0; --s) {
             final ContextAssignment assignment = stoppable.get(s);
             assignment.clear();
@@ -947,7 +990,6 @@
         preferredUidOnly.clear();
         mWorkCountTracker.resetStagingCount();
         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
-        noteConcurrency();
     }
 
     @GuardedBy("mLock")
@@ -1496,7 +1538,7 @@
 
     @NonNull
     private JobServiceContext createNewJobServiceContext() {
-        return new JobServiceContext(mService, this,
+        return mInjector.createJobServiceContext(mService, this,
                 IBatteryStats.Stub.asInterface(
                         ServiceManager.getService(BatteryStats.SERVICE_NAME)),
                 mService.mJobPackageTracker, mContext.getMainLooper());
@@ -1778,6 +1820,10 @@
     @VisibleForTesting
     static class WorkTypeConfig {
         @VisibleForTesting
+        static final String KEY_PREFIX_MAX = CONFIG_KEY_PREFIX_CONCURRENCY + "max_";
+        @VisibleForTesting
+        static final String KEY_PREFIX_MIN = CONFIG_KEY_PREFIX_CONCURRENCY + "min_";
+        @VisibleForTesting
         static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
         private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
         private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_";
@@ -2329,7 +2375,8 @@
         }
     }
 
-    private static final class ContextAssignment {
+    @VisibleForTesting
+    static final class ContextAssignment {
         public JobServiceContext context;
         public int preferredUid = JobServiceContext.NO_PREFERRED_UID;
         public int workType = WORK_TYPE_NONE;
@@ -2378,4 +2425,15 @@
         mActivePkgStats.add(userId, packageName, packageStats);
         return packageStats;
     }
+
+    @VisibleForTesting
+    static class Injector {
+        @NonNull
+        JobServiceContext createJobServiceContext(JobSchedulerService service,
+                JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
+                JobPackageTracker tracker, Looper looper) {
+            return new JobServiceContext(service, concurrencyManager, batteryStats,
+                    tracker, looper);
+        }
+    }
 }
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 f5c0ed9..bdd1fc54 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -970,12 +970,12 @@
                 // Has this package scheduled any jobs, such that we will take action
                 // if it were to be force-stopped?
                 if (pkgUid != -1) {
-                    List<JobStatus> jobsForUid;
+                    ArraySet<JobStatus> jobsForUid;
                     synchronized (mLock) {
                         jobsForUid = mJobs.getJobsByUid(pkgUid);
                     }
                     for (int i = jobsForUid.size() - 1; i >= 0; i--) {
-                        if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
+                        if (jobsForUid.valueAt(i).getSourcePackageName().equals(pkgName)) {
                             if (DEBUG) {
                                 Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
                                         + pkgUid + " has jobs");
@@ -1292,10 +1292,11 @@
 
     public List<JobInfo> getPendingJobs(int uid) {
         synchronized (mLock) {
-            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
+            ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
             ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+            // Write out for loop to avoid addAll() creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
-                JobStatus job = jobs.get(i);
+                final JobStatus job = jobs.valueAt(i);
                 outList.add(job.getJob());
             }
             return outList;
@@ -1304,9 +1305,9 @@
 
     public JobInfo getPendingJob(int uid, int jobId) {
         synchronized (mLock) {
-            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
+            ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
             for (int i = jobs.size() - 1; i >= 0; i--) {
-                JobStatus job = jobs.get(i);
+                JobStatus job = jobs.valueAt(i);
                 if (job.getJobId() == jobId) {
                     return job.getJob();
                 }
@@ -1348,7 +1349,7 @@
             Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
             return;
         }
-        final List<JobStatus> jobsForUid = new ArrayList<>();
+        final ArraySet<JobStatus> jobsForUid = new ArraySet<>();
         if (includeSchedulingApp) {
             mJobs.getJobsByUid(uid, jobsForUid);
         }
@@ -1356,7 +1357,7 @@
             mJobs.getJobsBySourceUid(uid, jobsForUid);
         }
         for (int i = jobsForUid.size() - 1; i >= 0; i--) {
-            final JobStatus job = jobsForUid.get(i);
+            final JobStatus job = jobsForUid.valueAt(i);
             final boolean shouldCancel =
                     (includeSchedulingApp
                             && job.getServiceComponent().getPackageName().equals(pkgName))
@@ -1368,14 +1369,16 @@
     }
 
     /**
-     * Entry point from client to cancel all jobs originating from their uid.
+     * Entry point from client to cancel all jobs scheduled for or from their uid.
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
      *
      * @param uid Uid to check against for removal of a job.
+     * @param includeSourceApp Whether to include jobs scheduled for this UID by another UID.
+     *                         If false, only jobs scheduled by this UID will be cancelled.
      */
-    public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
-            int internalReasonCode, String debugReason) {
+    public boolean cancelJobsForUid(int uid, boolean includeSourceApp,
+            @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
             return false;
@@ -1383,9 +1386,15 @@
 
         boolean jobsCanceled = false;
         synchronized (mLock) {
-            final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
+            final ArraySet<JobStatus> jobsForUid = new ArraySet<>();
+            // Get jobs scheduled by the app.
+            mJobs.getJobsByUid(uid, jobsForUid);
+            if (includeSourceApp) {
+                // Get jobs scheduled for the app by someone else.
+                mJobs.getJobsBySourceUid(uid, jobsForUid);
+            }
             for (int i = 0; i < jobsForUid.size(); i++) {
-                JobStatus toRemove = jobsForUid.get(i);
+                JobStatus toRemove = jobsForUid.valueAt(i);
                 cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason);
                 jobsCanceled = true;
             }
@@ -2220,6 +2229,7 @@
                         updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
                         if (disabled) {
                             cancelJobsForUid(uid,
+                                    /* includeSourceApp */ true,
                                     JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
                                     JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED,
                                     "uid gone");
@@ -2241,6 +2251,7 @@
                         final boolean disabled = message.arg2 != 0;
                         if (disabled) {
                             cancelJobsForUid(uid,
+                                    /* includeSourceApp */ true,
                                     JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
                                     JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED,
                                     "app uid idle");
@@ -2899,9 +2910,10 @@
         }
 
         @Override
-        public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
-                int internalReasonCode, String debugReason) {
-            JobSchedulerService.this.cancelJobsForUid(uid, reason, internalReasonCode, debugReason);
+        public void cancelJobsForUid(int uid, boolean includeProxiedJobs,
+                @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+            JobSchedulerService.this.cancelJobsForUid(uid,
+                    includeProxiedJobs, reason, internalReasonCode, debugReason);
         }
 
         @Override
@@ -3273,6 +3285,8 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 JobSchedulerService.this.cancelJobsForUid(uid,
+                        // Documentation says only jobs scheduled BY the app will be cancelled
+                        /* includeSourceApp */ false,
                         JobParameters.STOP_REASON_CANCELLED_BY_APP,
                         JobParameters.INTERNAL_STOP_REASON_CANCELED,
                         "cancelAll() called by app, callingUid=" + uid);
@@ -3484,7 +3498,9 @@
 
         if (!hasJobId) {
             pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
-            if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
+            if (!cancelJobsForUid(pkgUid,
+                    /* includeSourceApp */ false,
+                    JobParameters.STOP_REASON_USER,
                     JobParameters.INTERNAL_STOP_REASON_CANCELED,
                     "cancel shell command for package")) {
                 pw.println("No matching jobs found.");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index ff4d26d..f731b8d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -291,11 +291,11 @@
      * @return A list of all the jobs scheduled for the source app. Never null.
      */
     @NonNull
-    public List<JobStatus> getJobsBySourceUid(int sourceUid) {
+    public ArraySet<JobStatus> getJobsBySourceUid(int sourceUid) {
         return mJobSet.getJobsBySourceUid(sourceUid);
     }
 
-    public void getJobsBySourceUid(int sourceUid, @NonNull List<JobStatus> insertInto) {
+    public void getJobsBySourceUid(int sourceUid, @NonNull Set<JobStatus> insertInto) {
         mJobSet.getJobsBySourceUid(sourceUid, insertInto);
     }
 
@@ -304,11 +304,11 @@
      * @return All JobStatus objects for a given uid from the master list. Never null.
      */
     @NonNull
-    public List<JobStatus> getJobsByUid(int uid) {
+    public ArraySet<JobStatus> getJobsByUid(int uid) {
         return mJobSet.getJobsByUid(uid);
     }
 
-    public void getJobsByUid(int uid, @NonNull List<JobStatus> insertInto) {
+    public void getJobsByUid(int uid, @NonNull Set<JobStatus> insertInto) {
         mJobSet.getJobsByUid(uid, insertInto);
     }
 
@@ -1232,13 +1232,13 @@
             mJobsPerSourceUid = new SparseArray<>();
         }
 
-        public List<JobStatus> getJobsByUid(int uid) {
-            ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
+        public ArraySet<JobStatus> getJobsByUid(int uid) {
+            ArraySet<JobStatus> matchingJobs = new ArraySet<>();
             getJobsByUid(uid, matchingJobs);
             return matchingJobs;
         }
 
-        public void getJobsByUid(int uid, List<JobStatus> insertInto) {
+        public void getJobsByUid(int uid, Set<JobStatus> insertInto) {
             ArraySet<JobStatus> jobs = mJobs.get(uid);
             if (jobs != null) {
                 insertInto.addAll(jobs);
@@ -1246,13 +1246,13 @@
         }
 
         @NonNull
-        public List<JobStatus> getJobsBySourceUid(int sourceUid) {
-            final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+        public ArraySet<JobStatus> getJobsBySourceUid(int sourceUid) {
+            final ArraySet<JobStatus> result = new ArraySet<>();
             getJobsBySourceUid(sourceUid, result);
             return result;
         }
 
-        public void getJobsBySourceUid(int sourceUid, List<JobStatus> insertInto) {
+        public void getJobsBySourceUid(int sourceUid, Set<JobStatus> insertInto) {
             final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
             if (jobs != null) {
                 insertInto.addAll(jobs);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 9c16772..bbd661a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -50,7 +50,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 import java.util.function.Predicate;
 
 /**
@@ -397,10 +396,10 @@
             return;
         }
         final long nowElapsed = sElapsedRealtimeClock.millis();
-        List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
+        ArraySet<JobStatus> jobsByUid = mService.getJobStore().getJobsBySourceUid(uid);
         boolean hasPrefetch = false;
         for (int i = 0; i < jobsByUid.size(); i++) {
-            JobStatus js = jobsByUid.get(i);
+            JobStatus js = jobsByUid.valueAt(i);
             if (js.hasFlexibilityConstraint()) {
                 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
                 hasPrefetch |= js.getJob().isPrefetch();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 999a3c0..669234b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -126,7 +126,7 @@
     /**
      * Keeps track of how many flexible constraints must be satisfied for the job to execute.
      */
-    private int mNumRequiredFlexibleConstraints;
+    private final int mNumRequiredFlexibleConstraints;
 
     /**
      * Number of required flexible constraints that have been dropped.
@@ -343,7 +343,8 @@
     public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
 
     /** Minimum difference between start and end time to have flexible constraint */
-    private static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
+    @VisibleForTesting
+    static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
     /**
      * Versatile, persistable flags for a job that's updated within the system server,
      * as opposed to {@link JobInfo#flags} that's set by callers.
@@ -580,6 +581,8 @@
             mNumRequiredFlexibleConstraints =
                     NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
             requiredConstraints |= CONSTRAINT_FLEXIBLE;
+        } else {
+            mNumRequiredFlexibleConstraints = 0;
         }
 
         this.requiredConstraints = requiredConstraints;
@@ -1152,7 +1155,7 @@
 
     /** Returns the number of flexible job constraints required to be satisfied to execute */
     public int getNumRequiredFlexibleConstraints() {
-        return mNumRequiredFlexibleConstraints;
+        return mNumRequiredFlexibleConstraints - mNumDroppedFlexibleConstraints;
     }
 
     /**
@@ -1585,14 +1588,8 @@
 
     /** Adjusts the number of required flexible constraints by the given number */
     public void adjustNumRequiredFlexibleConstraints(int adjustment) {
-        mNumRequiredFlexibleConstraints += adjustment;
-        if (mNumRequiredFlexibleConstraints < 0) {
-            mNumRequiredFlexibleConstraints = 0;
-        }
-        mNumDroppedFlexibleConstraints -= adjustment;
-        if (mNumDroppedFlexibleConstraints < 0) {
-            mNumDroppedFlexibleConstraints = 0;
-        }
+        mNumDroppedFlexibleConstraints = Math.max(0, Math.min(mNumRequiredFlexibleConstraints,
+                mNumDroppedFlexibleConstraints - adjustment));
     }
 
     /**
diff --git a/core/api/current.txt b/core/api/current.txt
index 7bdaa2b..e5cc625 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -49403,6 +49403,8 @@
     field public static final int AXIS_GENERIC_7 = 38; // 0x26
     field public static final int AXIS_GENERIC_8 = 39; // 0x27
     field public static final int AXIS_GENERIC_9 = 40; // 0x28
+    field public static final int AXIS_GESTURE_X_OFFSET = 48; // 0x30
+    field public static final int AXIS_GESTURE_Y_OFFSET = 49; // 0x31
     field public static final int AXIS_HAT_X = 15; // 0xf
     field public static final int AXIS_HAT_Y = 16; // 0x10
     field public static final int AXIS_HSCROLL = 10; // 0xa
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 761d9fa..3dbfdae 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6511,6 +6511,7 @@
     field public static final int MIX_ROLE_PLAYERS = 0; // 0x0
     field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
     field public static final int RULE_MATCH_ATTRIBUTE_USAGE = 1; // 0x1
+    field public static final int RULE_MATCH_AUDIO_SESSION_ID = 16; // 0x10
     field public static final int RULE_MATCH_UID = 4; // 0x4
     field public static final int RULE_MATCH_USERID = 8; // 0x8
   }
@@ -9276,13 +9277,17 @@
   }
 
   public final class BugreportManager {
+    method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void preDumpUiData();
     method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence);
     method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
   }
 
   public final class BugreportParams {
     ctor public BugreportParams(int);
+    ctor public BugreportParams(int, int);
+    method public int getFlags();
     method public int getMode();
+    field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1
     field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
     field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
     field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5695874..74eb1c5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9623,21 +9623,16 @@
         @NonNull
         public ArrayList<Action> getActionsListWithSystemActions() {
             // Define the system actions we expect to see
-            final Action negativeAction = makeNegativeAction();
-            final Action answerAction = makeAnswerAction();
-            // Sort the expected actions into the correct order:
-            // * If there's no answer action, put the hang up / decline action at the end
-            // * Otherwise put the answer action at the end, and put the decline action at start.
-            final Action firstAction = answerAction == null ? null : negativeAction;
-            final Action lastAction = answerAction == null ? negativeAction : answerAction;
+            final Action firstAction = makeNegativeAction();
+            final Action lastAction = makeAnswerAction();
 
             // Start creating the result list.
             int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS;
             ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS);
-            if (firstAction != null) {
-                resultActions.add(firstAction);
-                --nonContextualActionSlotsRemaining;
-            }
+
+            // Always have a first action.
+            resultActions.add(firstAction);
+            --nonContextualActionSlotsRemaining;
 
             // Copy actions into the new list, correcting system actions.
             if (mBuilder.mActions != null) {
@@ -9653,14 +9648,14 @@
                         --nonContextualActionSlotsRemaining;
                     }
                     // If there's exactly one action slot left, fill it with the lastAction.
-                    if (nonContextualActionSlotsRemaining == 1) {
+                    if (lastAction != null && nonContextualActionSlotsRemaining == 1) {
                         resultActions.add(lastAction);
                         --nonContextualActionSlotsRemaining;
                     }
                 }
             }
             // If there are any action slots left, the lastAction still needs to be added.
-            if (nonContextualActionSlotsRemaining >= 1) {
+            if (lastAction != null && nonContextualActionSlotsRemaining >= 1) {
                 resultActions.add(lastAction);
             }
             return resultActions;
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 27f9f54..a51b9d3 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1406,17 +1406,6 @@
     }
 
     /**
-     * Return the number of entries in the cache.  This is used for testing and has package-only
-     * visibility.
-     * @hide
-     */
-    public int size() {
-        synchronized (mLock) {
-            return mCache.size();
-        }
-    }
-
-    /**
      * Returns a list of caches alive at the current time.
      */
     @GuardedBy("sGlobalLock")
@@ -1623,12 +1612,8 @@
      * @hide
      */
     public static void onTrimMemory() {
-        ArrayList<PropertyInvalidatedCache> activeCaches;
-        synchronized (sGlobalLock) {
-            activeCaches = getActiveCaches();
-        }
-        for (int i = 0; i < activeCaches.size(); i++) {
-            activeCaches.get(i).clear();
+        for (PropertyInvalidatedCache pic : getActiveCaches()) {
+            pic.clear();
         }
     }
 }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0af96c2..5b0bd96 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -175,23 +175,6 @@
             "file_patterns": [
                 "(/|^)KeyguardManager.java"
             ]
-        },
-        {
-            "name": "FrameworksCoreTests",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                },
-                {
-                    "include-filter": "android.app.PropertyInvalidatedCacheTest"
-                }
-            ],
-            "file_patterns": [
-                "(/|^)PropertyInvalidatedCache.java"
-            ]
         }
     ],
     "presubmit-large": [
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 0f7e01b..94275ae 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -31,8 +31,6 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 
-import com.android.server.SystemConfig;
-
 import java.util.List;
 
 /**
@@ -50,7 +48,7 @@
  * <overlayable name="OverlayableResourcesName" actor="overlay://namespace/actorName">
  * }</pre></p>
  *
- * <p>Actors are defined through {@link SystemConfig}. Only system packages can be used.
+ * <p>Actors are defined through SystemConfig. Only system packages can be used.
  * The namespace "android" is reserved for use by AOSP and any "android" definitions must
  * have an implementation on device that fulfill their intended functionality.</p>
  *
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index e13f60c..2c28268 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -45,7 +45,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForBoolean;
-import com.android.server.SystemConfig;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -803,7 +802,6 @@
      */
     public static final int PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE = 1 << 2;
 
-
     /**
      * If false, {@link android.view.KeyEvent#KEYCODE_BACK} related events will be forwarded to
      * the Activities, Dialogs and Views and {@link android.app.Activity#onBackPressed()},
@@ -815,12 +813,24 @@
      */
     public static final int PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK = 1 << 3;
 
+    /**
+     * Whether or not this package is allowed to access hidden APIs. Replacement for legacy
+     * implementation of {@link #isPackageWhitelistedForHiddenApis()}.
+     *
+     * This is an internal flag and should never be used outside of this class. The real API for
+     * the hidden API enforcement policy is {@link #getHiddenApiEnforcementPolicy()}.
+     *
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
             PRIVATE_FLAG_EXT_PROFILEABLE,
             PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION,
             PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
             PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
+            PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlagsExt {}
@@ -2222,7 +2232,7 @@
     }
 
     private boolean isPackageWhitelistedForHiddenApis() {
-        return SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(packageName);
+        return (privateFlagsExt & PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS) != 0;
     }
 
     /**
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index eddb519..5de6d73 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -36,6 +36,12 @@
      */
     public static final @NonNull String EXTRA_REQUEST_INFO =
             "android.credentials.ui.extra.REQUEST_INFO";
+    /**
+     * The intent extra key for the {@code ResultReceiver} object when launching the UX
+     * activities.
+     */
+    public static final @NonNull String EXTRA_RESULT_RECEIVER =
+            "android.credentials.ui.extra.RESULT_RECEIVER";
 
     /** Type value for an executeGetCredential request. */
     public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 861a850..8873807 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2609,31 +2609,31 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="left">Input Format</th>
-     * <th align="left">Output Format</th>
-     * <th align="left">Capability</th>
+     * <th style="text-align: left;">Input Format</th>
+     * <th style="text-align: left;">Output Format</th>
+     * <th style="text-align: left;">Capability</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
-     * <td align="left">PRIVATE_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
      * </tr>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="left">PRIVATE_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
      * </tr>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
-     * <td align="left">YUV_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: left;">YUV_REPROCESSING</td>
      * </tr>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="left">YUV_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: left;">YUV_REPROCESSING</td>
      * </tr>
      * </tbody>
      * </table>
@@ -2650,26 +2650,26 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="left">Input Format</th>
-     * <th align="left">Output Format</th>
-     * <th align="left">Capability</th>
+     * <th style="text-align: left;">Input Format</th>
+     * <th style="text-align: left;">Output Format</th>
+     * <th style="text-align: left;">Capability</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
-     * <td align="left">PRIVATE_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+     * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
      * </tr>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
-     * <td align="left">YUV_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: left;">YUV_REPROCESSING</td>
      * </tr>
      * <tr>
-     * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
-     * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
-     * <td align="left">YUV_REPROCESSING</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+     * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+     * <td style="text-align: left;">YUV_REPROCESSING</td>
      * </tr>
      * </tbody>
      * </table>
@@ -2705,60 +2705,60 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">Format</th>
-     * <th align="center">Size</th>
-     * <th align="center">Hardware Level</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">Format</th>
+     * <th style="text-align: center;">Size</th>
+     * <th style="text-align: center;">Hardware Level</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
-     * <td align="center">Any</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">1920x1080 (1080p)</td>
-     * <td align="center">Any</td>
-     * <td align="center">if 1080p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">1920x1080 (1080p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">1280x720 (720)</td>
-     * <td align="center">Any</td>
-     * <td align="center">if 720p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">1280x720 (720)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">640x480 (480p)</td>
-     * <td align="center">Any</td>
-     * <td align="center">if 480p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">640x480 (480p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">320x240 (240p)</td>
-     * <td align="center">Any</td>
-     * <td align="center">if 240p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">320x240 (240p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">all output sizes available for JPEG</td>
-     * <td align="center">FULL</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">all output sizes available for JPEG</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">all output sizes available for JPEG, up to the maximum video size</td>
-     * <td align="center">LIMITED</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">all output sizes available for JPEG, up to the maximum video size</td>
+     * <td style="text-align: center;">LIMITED</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">IMPLEMENTATION_DEFINED</td>
-     * <td align="center">same as YUV_420_888</td>
-     * <td align="center">Any</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">IMPLEMENTATION_DEFINED</td>
+     * <td style="text-align: center;">same as YUV_420_888</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * </tbody>
      * </table>
@@ -2773,66 +2773,66 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">Format</th>
-     * <th align="center">Size</th>
-     * <th align="center">Hardware Level</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">Format</th>
+     * <th style="text-align: center;">Size</th>
+     * <th style="text-align: center;">Hardware Level</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
-     * <td align="center">Any</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">JPEG</td>
-     * <td align="center">1920x1080 (1080p)</td>
-     * <td align="center">Any</td>
-     * <td align="center">if 1080p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">JPEG</td>
+     * <td style="text-align: center;">1920x1080 (1080p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
-     * <td align="center">FULL</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">1920x1080 (1080p)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 1080p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">1920x1080 (1080p)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">1280x720 (720)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 720p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">1280x720 (720)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">640x480 (480p)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 480p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">640x480 (480p)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">320x240 (240p)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 240p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">320x240 (240p)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">YUV_420_888</td>
-     * <td align="center">all output sizes available for FULL hardware level, up to the maximum video size</td>
-     * <td align="center">LIMITED</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">YUV_420_888</td>
+     * <td style="text-align: center;">all output sizes available for FULL hardware level, up to the maximum video size</td>
+     * <td style="text-align: center;">LIMITED</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">IMPLEMENTATION_DEFINED</td>
-     * <td align="center">same as YUV_420_888</td>
-     * <td align="center">Any</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">IMPLEMENTATION_DEFINED</td>
+     * <td style="text-align: center;">same as YUV_420_888</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * </tbody>
      * </table>
@@ -2987,66 +2987,66 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">Format</th>
-     * <th align="center">Size</th>
-     * <th align="center">Hardware Level</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">Format</th>
+     * <th style="text-align: center;">Size</th>
+     * <th style="text-align: center;">Hardware Level</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td>
-     * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
-     * <td align="center">Any</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td>
-     * <td align="center">1920x1080 (1080p)</td>
-     * <td align="center">Any</td>
-     * <td align="center">if 1080p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+     * <td style="text-align: center;">1920x1080 (1080p)</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
-     * <td align="center">FULL</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="center">1920x1080 (1080p)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 1080p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">1920x1080 (1080p)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 1080p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="center">1280x720 (720)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 720p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">1280x720 (720)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 720p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="center">640x480 (480p)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 480p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">640x480 (480p)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 480p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="center">320x240 (240p)</td>
-     * <td align="center">FULL</td>
-     * <td align="center">if 240p &lt;= activeArraySize</td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">320x240 (240p)</td>
+     * <td style="text-align: center;">FULL</td>
+     * <td style="text-align: center;">if 240p &lt;= activeArraySize</td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
-     * <td align="center">all output sizes available for FULL hardware level, up to the maximum video size</td>
-     * <td align="center">LIMITED</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+     * <td style="text-align: center;">all output sizes available for FULL hardware level, up to the maximum video size</td>
+     * <td style="text-align: center;">LIMITED</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
-     * <td align="center">{@link android.graphics.ImageFormat#PRIVATE }</td>
-     * <td align="center">same as YUV_420_888</td>
-     * <td align="center">Any</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+     * <td style="text-align: center;">same as YUV_420_888</td>
+     * <td style="text-align: center;">Any</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * </tbody>
      * </table>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3e1deb2..1a15596 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -990,18 +990,18 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center"></td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device auto exposure algorithm is disabled</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device auto exposure algorithm is disabled</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1009,120 +1009,120 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device initiates AE scan</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values changing</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device initiates AE scan</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values changing</td>
      * </tr>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Camera device finishes AE scan</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Good values, not changing</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Camera device finishes AE scan</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Good values, not changing</td>
      * </tr>
      * <tr>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Camera device finishes AE scan</td>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Converged but too dark w/o flash</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Camera device finishes AE scan</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Converged but too dark w/o flash</td>
      * </tr>
      * <tr>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Camera device initiates AE scan</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values changing</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Camera device initiates AE scan</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values changing</td>
      * </tr>
      * <tr>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Camera device initiates AE scan</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values changing</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Camera device initiates AE scan</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values changing</td>
      * </tr>
      * <tr>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values not good after unlock</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values not good after unlock</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Values good after unlock</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Values good after unlock</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Exposure good, but too dark</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Exposure good, but too dark</td>
      * </tr>
      * <tr>
-     * <td align="center">PRECAPTURE</td>
-     * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Ready for high-quality capture</td>
+     * <td style="text-align: center;">PRECAPTURE</td>
+     * <td style="text-align: center;">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Ready for high-quality capture</td>
      * </tr>
      * <tr>
-     * <td align="center">PRECAPTURE</td>
-     * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Ready for high-quality capture</td>
+     * <td style="text-align: center;">PRECAPTURE</td>
+     * <td style="text-align: center;">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Ready for high-quality capture</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">aeLock is ON and aePrecaptureTrigger is START</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">aeLock is ON and aePrecaptureTrigger is START</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Precapture trigger is ignored when AE is already locked</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Precapture trigger is ignored when AE is already locked</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state (excluding LOCKED)</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
-     * <td align="center">PRECAPTURE</td>
-     * <td align="center">Start AE precapture metering sequence</td>
+     * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
+     * <td style="text-align: center;">PRECAPTURE</td>
+     * <td style="text-align: center;">Start AE precapture metering sequence</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state (excluding LOCKED)</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Currently active precapture metering sequence is canceled</td>
+     * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Currently active precapture metering sequence is canceled</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1138,54 +1138,54 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device finished AE scan</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device finished AE scan</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Values are already good, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state (excluding LOCKED)</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state (excluding LOCKED)</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Converged after a precapture sequence, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state (excluding LOCKED)</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state (excluding LOCKED)</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Camera device finished AE scan</td>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Camera device finished AE scan</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">FLASH_REQUIRED</td>
-     * <td align="center">Camera device finished AE scan</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Converged after a new scan, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">FLASH_REQUIRED</td>
+     * <td style="text-align: center;">Camera device finished AE scan</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Converged after a new scan, transient states are skipped by camera device.</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1413,18 +1413,18 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center"></td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Never changes</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Never changes</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1432,66 +1432,66 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">ACTIVE_SCAN</td>
-     * <td align="center">Start AF sweep, Lens now moving</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">ACTIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF sweep, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">ACTIVE_SCAN</td>
-     * <td align="center">AF sweep done</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Focused, Lens now locked</td>
+     * <td style="text-align: center;">ACTIVE_SCAN</td>
+     * <td style="text-align: center;">AF sweep done</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Focused, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">ACTIVE_SCAN</td>
-     * <td align="center">AF sweep done</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">Not focused, Lens now locked</td>
+     * <td style="text-align: center;">ACTIVE_SCAN</td>
+     * <td style="text-align: center;">AF sweep done</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Not focused, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">ACTIVE_SCAN</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Cancel/reset AF, Lens now locked</td>
+     * <td style="text-align: center;">ACTIVE_SCAN</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Cancel/reset AF, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Cancel/reset AF</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Cancel/reset AF</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">ACTIVE_SCAN</td>
-     * <td align="center">Start new sweep, Lens now moving</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">ACTIVE_SCAN</td>
+     * <td style="text-align: center;">Start new sweep, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Cancel/reset AF</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Cancel/reset AF</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">ACTIVE_SCAN</td>
-     * <td align="center">Start new sweep, Lens now moving</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">ACTIVE_SCAN</td>
+     * <td style="text-align: center;">Start new sweep, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">Any state</td>
-     * <td align="center">Mode change</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center"></td>
+     * <td style="text-align: center;">Any state</td>
+     * <td style="text-align: center;">Mode change</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;"></td>
      * </tr>
      * </tbody>
      * </table>
@@ -1504,36 +1504,36 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Focus is already good or good after a scan, lens is now locked.</td>
      * </tr>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">Focus failed after a scan, lens is now locked.</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Focus failed after a scan, lens is now locked.</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Focus is already good or good after a scan, lens is now locked.</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Focus is good after a scan, lens is not locked.</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Focus is good after a scan, lens is not locked.</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1541,102 +1541,102 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device initiates new scan</td>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Start AF scan, Lens now moving</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device initiates new scan</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF scan, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF state query, Lens now locked</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF state query, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Camera device completes current scan</td>
-     * <td align="center">PASSIVE_FOCUSED</td>
-     * <td align="center">End AF scan, Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Camera device completes current scan</td>
+     * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+     * <td style="text-align: center;">End AF scan, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Camera device fails current scan</td>
-     * <td align="center">PASSIVE_UNFOCUSED</td>
-     * <td align="center">End AF scan, Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Camera device fails current scan</td>
+     * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+     * <td style="text-align: center;">End AF scan, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Immediate transition, if focus is good. Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Immediate transition, if focus is good. Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">Immediate transition, if focus is bad. Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Immediate transition, if focus is bad. Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Reset lens position, Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Reset lens position, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_FOCUSED</td>
-     * <td align="center">Camera device initiates new scan</td>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Start AF scan, Lens now moving</td>
+     * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+     * <td style="text-align: center;">Camera device initiates new scan</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF scan, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_UNFOCUSED</td>
-     * <td align="center">Camera device initiates new scan</td>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Start AF scan, Lens now moving</td>
+     * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+     * <td style="text-align: center;">Camera device initiates new scan</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF scan, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_FOCUSED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Immediate transition, lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Immediate transition, lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_UNFOCUSED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">Immediate transition, lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Immediate transition, lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">No effect</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">No effect</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Restart AF scan</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Restart AF scan</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">No effect</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">No effect</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Restart AF scan</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Restart AF scan</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1644,102 +1644,102 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device initiates new scan</td>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Start AF scan, Lens now moving</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device initiates new scan</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF scan, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF state query, Lens now locked</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF state query, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Camera device completes current scan</td>
-     * <td align="center">PASSIVE_FOCUSED</td>
-     * <td align="center">End AF scan, Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Camera device completes current scan</td>
+     * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+     * <td style="text-align: center;">End AF scan, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Camera device fails current scan</td>
-     * <td align="center">PASSIVE_UNFOCUSED</td>
-     * <td align="center">End AF scan, Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Camera device fails current scan</td>
+     * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+     * <td style="text-align: center;">End AF scan, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Eventual transition once the focus is good. Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Eventual transition once the focus is good. Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">Eventual transition if cannot find focus. Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Eventual transition if cannot find focus. Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Reset lens position, Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Reset lens position, Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_FOCUSED</td>
-     * <td align="center">Camera device initiates new scan</td>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Start AF scan, Lens now moving</td>
+     * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+     * <td style="text-align: center;">Camera device initiates new scan</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF scan, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_UNFOCUSED</td>
-     * <td align="center">Camera device initiates new scan</td>
-     * <td align="center">PASSIVE_SCAN</td>
-     * <td align="center">Start AF scan, Lens now moving</td>
+     * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+     * <td style="text-align: center;">Camera device initiates new scan</td>
+     * <td style="text-align: center;">PASSIVE_SCAN</td>
+     * <td style="text-align: center;">Start AF scan, Lens now moving</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_FOCUSED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">Immediate trans. Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Immediate trans. Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">PASSIVE_UNFOCUSED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">Immediate trans. Lens now locked</td>
+     * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">Immediate trans. Lens now locked</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">No effect</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">No effect</td>
      * </tr>
      * <tr>
-     * <td align="center">FOCUSED_LOCKED</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Restart AF scan</td>
+     * <td style="text-align: center;">FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Restart AF scan</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_TRIGGER</td>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">No effect</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_TRIGGER</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">No effect</td>
      * </tr>
      * <tr>
-     * <td align="center">NOT_FOCUSED_LOCKED</td>
-     * <td align="center">AF_CANCEL</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Restart AF scan</td>
+     * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+     * <td style="text-align: center;">AF_CANCEL</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Restart AF scan</td>
      * </tr>
      * </tbody>
      * </table>
@@ -1751,30 +1751,30 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">any state</td>
-     * <td align="center">CAF--&gt;AUTO mode switch</td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Mode switch without trigger, initial state must be INACTIVE</td>
+     * <td style="text-align: center;">any state</td>
+     * <td style="text-align: center;">CAF--&gt;AUTO mode switch</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Mode switch without trigger, initial state must be INACTIVE</td>
      * </tr>
      * <tr>
-     * <td align="center">any state</td>
-     * <td align="center">CAF--&gt;AUTO mode switch with AF_TRIGGER</td>
-     * <td align="center">trigger-reachable states from INACTIVE</td>
-     * <td align="center">Mode switch with trigger, INACTIVE is skipped</td>
+     * <td style="text-align: center;">any state</td>
+     * <td style="text-align: center;">CAF--&gt;AUTO mode switch with AF_TRIGGER</td>
+     * <td style="text-align: center;">trigger-reachable states from INACTIVE</td>
+     * <td style="text-align: center;">Mode switch with trigger, INACTIVE is skipped</td>
      * </tr>
      * <tr>
-     * <td align="center">any state</td>
-     * <td align="center">AUTO--&gt;CAF mode switch</td>
-     * <td align="center">passively reachable states from INACTIVE</td>
-     * <td align="center">Mode switch without trigger, passive transient state is skipped</td>
+     * <td style="text-align: center;">any state</td>
+     * <td style="text-align: center;">AUTO--&gt;CAF mode switch</td>
+     * <td style="text-align: center;">passively reachable states from INACTIVE</td>
+     * <td style="text-align: center;">Mode switch without trigger, passive transient state is skipped</td>
      * </tr>
      * </tbody>
      * </table>
@@ -2053,18 +2053,18 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center"></td>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device auto white balance algorithm is disabled</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device auto white balance algorithm is disabled</td>
      * </tr>
      * </tbody>
      * </table>
@@ -2072,54 +2072,54 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device initiates AWB scan</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values changing</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device initiates AWB scan</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values changing</td>
      * </tr>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Camera device finishes AWB scan</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Good values, not changing</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Camera device finishes AWB scan</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Good values, not changing</td>
      * </tr>
      * <tr>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Camera device initiates AWB scan</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values changing</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Camera device initiates AWB scan</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values changing</td>
      * </tr>
      * <tr>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
-     * <td align="center">LOCKED</td>
-     * <td align="center">Values locked</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">Values locked</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
-     * <td align="center">SEARCHING</td>
-     * <td align="center">Values not good after unlock</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+     * <td style="text-align: center;">SEARCHING</td>
+     * <td style="text-align: center;">Values not good after unlock</td>
      * </tr>
      * </tbody>
      * </table>
@@ -2132,24 +2132,24 @@
      * <table>
      * <thead>
      * <tr>
-     * <th align="center">State</th>
-     * <th align="center">Transition Cause</th>
-     * <th align="center">New State</th>
-     * <th align="center">Notes</th>
+     * <th style="text-align: center;">State</th>
+     * <th style="text-align: center;">Transition Cause</th>
+     * <th style="text-align: center;">New State</th>
+     * <th style="text-align: center;">Notes</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
-     * <td align="center">INACTIVE</td>
-     * <td align="center">Camera device finished AWB scan</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">INACTIVE</td>
+     * <td style="text-align: center;">Camera device finished AWB scan</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Values are already good, transient states are skipped by camera device.</td>
      * </tr>
      * <tr>
-     * <td align="center">LOCKED</td>
-     * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
-     * <td align="center">CONVERGED</td>
-     * <td align="center">Values good after unlock, transient states are skipped by camera device.</td>
+     * <td style="text-align: center;">LOCKED</td>
+     * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+     * <td style="text-align: center;">CONVERGED</td>
+     * <td style="text-align: center;">Values good after unlock, transient states are skipped by camera device.</td>
      * </tr>
      * </tbody>
      * </table>
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 73bb8d5..222e88f 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -148,6 +148,29 @@
     }
 
     /**
+     * Speculatively pre-dumps UI data for a bugreport request that might come later.
+     *
+     * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short
+     * ring buffers that might get lost by the time the actual bugreport is requested.
+     *
+     * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following
+     * conditions are met:
+     * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified.
+     * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    @WorkerThread
+    public void preDumpUiData() {
+        try {
+            mBinder.preDumpUiData(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Starts a bugreport.
      *
      * <p>This starts a bugreport in the background. However the call itself can take several
@@ -198,6 +221,7 @@
                     bugreportFd.getFileDescriptor(),
                     screenshotFd.getFileDescriptor(),
                     params.getMode(),
+                    params.getFlags(),
                     dsListener,
                     isScreenshotRequested);
         } catch (RemoteException e) {
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 279ccae..990883f 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -30,16 +30,46 @@
 @SystemApi
 public final class BugreportParams {
     private final int mMode;
+    private final int mFlags;
 
+    /**
+     * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+     *
+     * @param mode of the bugreport to request
+     */
     public BugreportParams(@BugreportMode int mode) {
         mMode = mode;
+        mFlags = 0;
     }
 
+    /**
+     * Constructs a BugreportParams object to specify what kind of bugreport should be taken.
+     *
+     * @param mode of the bugreport to request
+     * @param flags to customize the bugreport request
+     */
+    public BugreportParams(@BugreportMode int mode, @BugreportFlag int flags) {
+        mMode = mode;
+        mFlags = flags;
+    }
+
+    /**
+     * Returns the mode of the bugreport to request.
+     */
+    @BugreportMode
     public int getMode() {
         return mMode;
     }
 
     /**
+     * Returns the flags to customize the bugreport request.
+     */
+    @BugreportFlag
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
      * Defines acceptable types of bugreports.
      * @hide
      */
@@ -88,4 +118,21 @@
      * Wifi.
      */
     public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+
+    /**
+     * Defines acceptable flags for customizing bugreport requests.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
+            BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+    })
+    public @interface BugreportFlag {}
+
+    /**
+     * Flag for reusing pre-dumped UI data. The pre-dump and bugreport request calls must be
+     * performed by the same UID, otherwise the flag is ignored.
+     */
+    public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA =
+            IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA;
 }
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 15e3ce2..61b24aa 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -47,4 +47,9 @@
      * @see SystemConfigManager#getEnabledComponentOverrides
      */
     List<ComponentName> getEnabledComponentOverrides(String packageName);
+
+    /**
+     * @see SystemConfigManager#getDefaultVrComponents
+     */
+    List<ComponentName> getDefaultVrComponents();
 }
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index cde2063..77843d9 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -147,4 +147,18 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Return the components that are enabled by default as VR mode listener services.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)
+    public List<ComponentName> getDefaultVrComponents() {
+        try {
+            return mInterface.getDefaultVrComponents();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return Collections.emptyList();
+    }
 }
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 1afd048..fa647d7 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -26,8 +26,6 @@
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.inputmethod.InputMethodDebug;
-import com.android.internal.inputmethod.StartInputFlags;
-import com.android.internal.inputmethod.StartInputReason;
 
 /**
  * Responsible for IME focus handling inside {@link ViewRootImpl}.
@@ -67,7 +65,8 @@
 
     @UiThread
     void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
-        final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
+        final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
+                windowAttribute.flags);
         if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
             return;
         }
@@ -76,30 +75,20 @@
         }
         mHasImeFocus = hasImeFocus;
         if (mHasImeFocus) {
-            onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
-            onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
-                    windowAttribute);
+            getImmDelegate().onPreWindowGainedFocus(mViewRootImpl);
+            final View focusedView = mViewRootImpl.mView.findFocus();
+            View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
+            getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute);
         }
     }
 
     @UiThread
     void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
-        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+        mHasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(windowAttribute.flags);
+        if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
             return;
         }
-        if (hasWindowFocus) {
-            getImmDelegate().setCurrentRootView(mViewRootImpl);
-        }
-    }
-
-    @UiThread
-    boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
-        final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
-                windowAttribute.flags);
-        if (force) {
-            mHasImeFocus = hasImeFocus;
-        }
-        return hasImeFocus;
+        getImmDelegate().onPreWindowGainedFocus(mViewRootImpl);
     }
 
     @UiThread
@@ -115,7 +104,7 @@
                     windowAttribute.softInputMode));
         }
 
-        getImmDelegate().onPostWindowFocus(viewForWindowFocus, windowAttribute);
+        getImmDelegate().onPostWindowGainedFocus(viewForWindowFocus, windowAttribute);
     }
 
     /**
@@ -189,14 +178,8 @@
      * @hide
      */
     public interface InputMethodManagerDelegate {
-        /**
-         * Starts the input connection.
-         */
-        boolean startInput(@StartInputReason int startInputReason, View focusedView,
-                @StartInputFlags int startInputFlags,
-                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
-
-        void onPostWindowFocus(View viewForWindowFocus,
+        void onPreWindowGainedFocus(ViewRootImpl viewRootImpl);
+        void onPostWindowGainedFocus(View viewForWindowFocus,
                 @NonNull WindowManager.LayoutParams windowAttribute);
         void onViewFocusChanged(@NonNull View view, boolean hasFocus);
         boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl);
@@ -204,7 +187,6 @@
         void onWindowDismissed(ViewRootImpl viewRootImpl);
 
         void finishInputAndReportToIme();
-        void setCurrentRootView(ViewRootImpl rootView);
         boolean isCurrentRootView(ViewRootImpl rootView);
     }
 
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index b5dff5e..ceab310 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1272,6 +1272,25 @@
      */
     public static final int AXIS_GENERIC_16 = 47;
 
+    /**
+     * Axis constant: X gesture offset axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a touch pad, reports the distance that a swipe gesture has moved in the X axis, as a
+     * proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a
+     * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
+     * -0.1.
+     * </ul>
+     */
+    public static final int AXIS_GESTURE_X_OFFSET = 48;
+
+    /**
+     * Axis constant: Y gesture offset axis of a motion event.
+     *
+     * The same as {@link #AXIS_GESTURE_X_OFFSET}, but for the Y axis.
+     */
+    public static final int AXIS_GESTURE_Y_OFFSET = 49;
+
     // NOTE: If you add a new axis here you must also add it to:
     //  frameworks/native/include/android/input.h
     //  frameworks/native/libs/input/InputEventLabels.cpp
@@ -1325,6 +1344,8 @@
         names.append(AXIS_GENERIC_14, "AXIS_GENERIC_14");
         names.append(AXIS_GENERIC_15, "AXIS_GENERIC_15");
         names.append(AXIS_GENERIC_16, "AXIS_GENERIC_16");
+        names.append(AXIS_GESTURE_X_OFFSET, "AXIS_GESTURE_X_OFFSET");
+        names.append(AXIS_GESTURE_Y_OFFSET, "AXIS_GESTURE_Y_OFFSET");
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b2b5f13..58c8126 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -286,7 +286,7 @@
      * @hide
      */
     public static final boolean LOCAL_LAYOUT =
-            SystemProperties.getBoolean("persist.debug.local_layout", false);
+            SystemProperties.getBoolean("persist.debug.local_layout", true);
 
     /**
      * Set this system property to true to force the view hierarchy to render
@@ -3798,7 +3798,6 @@
             }
 
             mAttachInfo.mHasWindowFocus = hasWindowFocus;
-            mImeFocusController.updateImeFocusable(mWindowAttributes, true /* force */);
             mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
 
             if (mView != null) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 08a7583..b883d0c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -46,6 +46,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UiThread;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.compat.annotation.ChangeId;
@@ -737,28 +738,6 @@
 
     private final class DelegateImpl implements
             ImeFocusController.InputMethodManagerDelegate {
-        /**
-         * Used by {@link ImeFocusController} to start input connection.
-         */
-        @Override
-        public boolean startInput(@StartInputReason int startInputReason, View focusedView,
-                @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
-                int windowFlags) {
-            ImeTracing.getInstance().triggerClientDump(
-                    "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this,
-                    null /* icProto */);
-            return startInputOnWindowFocusGainInternal(startInputReason, focusedView,
-                    startInputFlags, softInputMode, windowFlags);
-        }
-
-        private void finishInput() {
-            ImeTracing.getInstance().triggerClientDump(
-                    "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this,
-                    null /* icProto */);
-            synchronized (mH) {
-                finishInputLocked();
-            }
-        }
 
         /**
          * Used by {@link ImeFocusController} to finish input connection and callback
@@ -781,12 +760,19 @@
         }
 
         @Override
-        public void onPostWindowFocus(View viewForWindowFocus,
+        public void onPreWindowGainedFocus(ViewRootImpl viewRootImpl) {
+            synchronized (mH) {
+                setCurrentRootViewLocked(viewRootImpl);
+            }
+        }
+
+        @Override
+        public void onPostWindowGainedFocus(View viewForWindowFocus,
                 @NonNull WindowManager.LayoutParams windowAttribute) {
             boolean forceFocus = false;
             synchronized (mH) {
                 // Update mNextServedView when focusedView changed.
-                onViewFocusChanged(viewForWindowFocus, true);
+                onViewFocusChangedInternal(viewForWindowFocus, true);
 
                 // Starting new input when the next focused view is same as served view but the
                 // currently active connection (if any) is not associated with it.
@@ -861,35 +847,7 @@
 
         @Override
         public void onViewFocusChanged(@Nullable View view, boolean hasFocus) {
-            if (view == null || view.isTemporarilyDetached()) {
-                return;
-            }
-            final ViewRootImpl viewRootImpl = view.getViewRootImpl();
-            synchronized (mH) {
-                if (mCurRootView != viewRootImpl) {
-                    return;
-                }
-                if (!view.hasImeFocus() || !view.hasWindowFocus()) {
-                    return;
-                }
-                if (DEBUG) {
-                    Log.d(TAG, "onViewFocusChanged, view=" + InputMethodDebug.dumpViewInfo(view));
-                }
-
-                // We don't need to track the next served view when the view lost focus here
-                // because:
-                // 1) The current view focus may be cleared temporary when in touch mode, closing
-                //    input at this moment isn't the right way.
-                // 2) We only care about the served view change when it focused, since changing
-                //    input connection when the focus target changed is reasonable.
-                // 3) Setting the next served view as null when no more served view should be
-                //    handled in other special events (e.g. view detached from window or the window
-                //    dismissed).
-                if (hasFocus) {
-                    mNextServedView = view;
-                }
-            }
-            viewRootImpl.dispatchCheckFocus();
+            onViewFocusChangedInternal(view, hasFocus);
         }
 
         @Override
@@ -912,7 +870,7 @@
                 }
                 // Close the connection when no next served view coming.
                 if (mNextServedView == null) {
-                    finishInput();
+                    finishInputLocked();
                     closeCurrentInput();
                     return false;
                 }
@@ -923,7 +881,8 @@
             }
 
             if (startInput) {
-                startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */,
+                startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS,
+                        null /* focusedView */,
                         0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
             }
             return true;
@@ -951,21 +910,16 @@
                     return;
                 }
                 if (mServedView != null) {
-                    finishInput();
+                    finishInputLocked();
                 }
-                setCurrentRootView(null);
+                setCurrentRootViewLocked(null);
             }
         }
 
-        /**
-         * Used for {@link ImeFocusController} to set the current focused root view.
-         */
-        @Override
-        public void setCurrentRootView(ViewRootImpl rootView) {
-            synchronized (mH) {
-                mImeDispatcher.switchRootView(mCurRootView, rootView);
-                mCurRootView = rootView;
-            }
+        @GuardedBy("mH")
+        private void setCurrentRootViewLocked(ViewRootImpl rootView) {
+            mImeDispatcher.switchRootView(mCurRootView, rootView);
+            mCurRootView = rootView;
         }
 
         /**
@@ -2413,7 +2367,7 @@
     }
 
     /**
-     * Called when {@link DelegateImpl#startInput}, {@link #restartInput(View)},
+     * Called when {@link DelegateImpl#checkFocus}, {@link #restartInput(View)},
      * {@link #MSG_BIND} or {@link #MSG_UNBIND}.
      * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
      * background thread may blocked by other methods which already inside {@code mH} lock.
@@ -2750,6 +2704,40 @@
         }
     }
 
+    @UiThread
+    private void onViewFocusChangedInternal(@Nullable View view, boolean hasFocus) {
+        if (view == null || view.isTemporarilyDetached()) {
+            return;
+        }
+        final ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        synchronized (mH) {
+            if (mCurRootView != viewRootImpl) {
+                return;
+            }
+            if (!view.hasImeFocus() || !view.hasWindowFocus()) {
+                return;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "onViewFocusChangedInternal, view="
+                        + InputMethodDebug.dumpViewInfo(view));
+            }
+
+            // We don't need to track the next served view when the view lost focus here
+            // because:
+            // 1) The current view focus may be cleared temporary when in touch mode, closing
+            //    input at this moment isn't the right way.
+            // 2) We only care about the served view change when it focused, since changing
+            //    input connection when the focus target changed is reasonable.
+            // 3) Setting the next served view as null when no more served view should be
+            //    handled in other special events (e.g. view detached from window or the window
+            //    dismissed).
+            if (hasFocus) {
+                mNextServedView = view;
+            }
+        }
+        viewRootImpl.dispatchCheckFocus();
+    }
+
     @UnsupportedAppUsage
     void closeCurrentInput() {
         synchronized (mH) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 77317d1..2433a05 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2160,6 +2160,11 @@
             nCriterion.mValue.mUserId =
                     env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
             break;
+        case RULE_MATCH_AUDIO_SESSION_ID: {
+            jint jAudioSessionId =
+                    env->GetIntField(jCriterion, gAudioMixMatchCriterionFields.mIntProp);
+            nCriterion.mValue.mAudioSessionId = static_cast<audio_session_t>(jAudioSessionId);
+        } break;
         case RULE_MATCH_ATTRIBUTE_USAGE:
         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
             jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index acf4da6..2cd9f89 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -257,6 +257,13 @@
         return INSTALL_FAILED_CONTAINER_ERROR;
     }
 
+    if (fsync(fd) < 0) {
+        ALOGE("Coulnd't fsync temporary file name: %s: %s\n", localTmpFileName, strerror(errno));
+        close(fd);
+        unlink(localTmpFileName);
+        return INSTALL_FAILED_INTERNAL_ERROR;
+    }
+
     close(fd);
 
     // Set the modification time for this file to the ZIP's mod time.
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index daae957..180a312 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -36,7 +36,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.StrictMode;
-import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -48,6 +47,9 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -57,11 +59,22 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
 
 /**
  * Tests for BugreportManager API.
@@ -74,6 +87,7 @@
     private static final String TAG = "BugreportManagerTest";
     private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
     private static final long DUMPSTATE_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+    private static final long DUMPSTATE_TEARDOWN_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
     private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
 
 
@@ -89,6 +103,18 @@
     private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
     private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
 
+    private static final Path[] UI_TRACES_PREDUMPED = {
+            Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"),
+            Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
+            Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
+            Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
+            Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
+            Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
+    };
+    private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
+            Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
+    };
+
     private Handler mHandler;
     private Executor mExecutor;
     private BugreportManager mBrm;
@@ -124,7 +150,6 @@
         FileUtils.closeQuietly(mScreenshotFd);
     }
 
-
     @Test
     public void normalFlow_wifi() throws Exception {
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
@@ -175,6 +200,66 @@
         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
     }
 
+    @LargeTest
+    @Test
+    public void preDumpUiData_then_fullWithUsePreDumpFlag() throws Exception {
+        startPreDumpedUiTraces();
+
+        mBrm.preDumpUiData();
+        waitTillDumpstateExitedOrTimeout();
+        List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+                callback);
+        shareConsentDialog(ConsentReply.ALLOW);
+        waitTillDoneOrTimeout(callback);
+
+        stopPreDumpedUiTraces();
+
+        assertThat(callback.isDone()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdsAreClosed(mBugreportFd);
+
+        assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+        assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
+
+        List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+        assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles);
+    }
+
+    @LargeTest
+    @Test
+    public void preDumpData_then_fullWithoutUsePreDumpFlag_ignoresPreDump() throws Exception {
+        startPreDumpedUiTraces();
+
+        // Simulate pre-dump, instead of taking a real one.
+        // In some corner cases, data dumped as part of the full bugreport could be the same as the
+        // pre-dumped data and this test would fail. Hence, here we create fake/artificial
+        // pre-dumped data that we know it won't match with the full bugreport data.
+        createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system");
+
+        List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED);
+
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        mBrm.startBugreport(mBugreportFd, null, full(), mExecutor,
+                callback);
+        shareConsentDialog(ConsentReply.ALLOW);
+        waitTillDoneOrTimeout(callback);
+
+        stopPreDumpedUiTraces();
+
+        assertThat(callback.isDone()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdsAreClosed(mBugreportFd);
+
+        assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+        assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT);
+
+        List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED);
+        assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
+    }
+
     @Test
     public void simultaneousBugreportsNotAllowed() throws Exception {
         // Start bugreport #1
@@ -384,21 +469,151 @@
         }
         return bm;
     }
-
     private static File createTempFile(String prefix, String extension) throws Exception {
         final File f = File.createTempFile(prefix, extension);
         f.setReadable(true, true);
         f.setWritable(true, true);
-
         f.deleteOnExit();
         return f;
     }
 
+    private static void startPreDumpedUiTraces() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "cmd input_method tracing start"
+        );
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "cmd window tracing start"
+        );
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "service call SurfaceFlinger 1025 i32 1"
+        );
+    }
+
+    private static void stopPreDumpedUiTraces() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "cmd input_method tracing stop"
+        );
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "cmd window tracing stop"
+        );
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "service call SurfaceFlinger 1025 i32 0"
+        );
+    }
+
+    private void assertThatBugreportContainsFiles(Path[] paths)
+            throws IOException {
+        List<Path> entries = listZipArchiveEntries(mBugreportFile);
+        for (Path pathInDevice : paths) {
+            Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
+            assertThat(entries).contains(pathInArchive);
+        }
+    }
+
+    private List<File> extractFilesFromBugreport(Path[] paths) throws Exception {
+        List<File> files = new ArrayList<File>();
+        for (Path pathInDevice : paths) {
+            Path pathInArchive = Paths.get("FS" + pathInDevice.toString());
+            files.add(extractZipArchiveEntry(mBugreportFile, pathInArchive));
+        }
+        return files;
+    }
+
+    private static List<Path> listZipArchiveEntries(File archive) throws IOException {
+        ArrayList<Path> entries = new ArrayList<>();
+
+        ZipInputStream stream = new ZipInputStream(
+                new BufferedInputStream(new FileInputStream(archive)));
+
+        for (ZipEntry entry = stream.getNextEntry(); entry != null; entry = stream.getNextEntry()) {
+            entries.add(Paths.get(entry.toString()));
+        }
+
+        return entries;
+    }
+
+    private static File extractZipArchiveEntry(File archive, Path entryToExtract)
+            throws Exception {
+        File extractedFile = createTempFile(entryToExtract.getFileName().toString(), ".extracted");
+
+        ZipInputStream is = new ZipInputStream(new FileInputStream(archive));
+        boolean hasFoundEntry = false;
+
+        for (ZipEntry entry = is.getNextEntry(); entry != null; entry = is.getNextEntry()) {
+            if (entry.toString().equals(entryToExtract.toString())) {
+                BufferedOutputStream os =
+                        new BufferedOutputStream(new FileOutputStream(extractedFile));
+                ByteStreams.copy(is, os);
+                os.close();
+                hasFoundEntry = true;
+                break;
+            }
+
+            ByteStreams.exhaust(is); // skip entry
+        }
+
+        is.closeEntry();
+        is.close();
+
+        assertThat(hasFoundEntry).isTrue();
+
+        return extractedFile;
+    }
+
+    private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception {
+        File src = createTempFile("fake", ".data");
+        Files.write("fake data".getBytes(StandardCharsets.UTF_8), src);
+
+        for (Path path : paths) {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                    "install -m 611 -o " + owner + " -g " + owner
+                    + " " + src.getAbsolutePath() + " " + path.toString()
+            );
+        }
+    }
+
+    private static List<File> copyFilesAsRoot(Path[] paths) throws Exception {
+        ArrayList<File> files = new ArrayList<File>();
+        for (Path src : paths) {
+            File dst = createTempFile(src.getFileName().toString(), ".copy");
+            InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                    "cp " + src.toString() + " " + dst.getAbsolutePath()
+            );
+            files.add(dst);
+        }
+        return files;
+    }
+
     private static ParcelFileDescriptor parcelFd(File file) throws Exception {
         return ParcelFileDescriptor.open(file,
                 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
     }
 
+    private static void assertThatAllFileContentsAreEqual(List<File> actual, List<File> expected)
+            throws IOException {
+        if (actual.size() != expected.size()) {
+            fail("File lists have different size");
+        }
+        for (int i = 0; i < actual.size(); ++i) {
+            if (!Files.equal(actual.get(i), expected.get(i))) {
+                fail("Contents of " + actual.get(i).toString()
+                        + " != " + expected.get(i).toString());
+            }
+        }
+    }
+
+    private static void assertThatAllFileContentsAreDifferent(List<File> a, List<File> b)
+            throws IOException {
+        if (a.size() != b.size()) {
+            fail("File lists have different size");
+        }
+        for (int i = 0; i < a.size(); ++i) {
+            if (Files.equal(a.get(i), b.get(i))) {
+                fail("Contents of " + a.get(i).toString() + " == " + b.get(i).toString());
+            }
+        }
+    }
+
     private static void dropPermissions() {
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .dropShellPermissionIdentity();
@@ -410,21 +625,16 @@
     }
 
     private static boolean isDumpstateRunning() {
-        String[] output;
+        String output;
         try {
-            output =
-                    UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-                            .executeShellCommand("ps -A -o NAME | grep dumpstate")
-                            .trim()
-                            .split("\n");
+            output = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                    .executeShellCommand("service list | grep dumpstate");
         } catch (IOException e) {
             Log.w(TAG, "Failed to check if dumpstate is running", e);
             return false;
         }
-        for (String line : output) {
-            // Check for an exact match since there may be other things that contain "dumpstate" as
-            // a substring (e.g. the dumpstate HAL).
-            if (TextUtils.equals("dumpstate", line)) {
+        for (String line : output.trim().split("\n")) {
+            if (line.matches("^.*\\s+dumpstate:\\s+\\[.*\\]$")) {
                 return true;
             }
         }
@@ -449,6 +659,17 @@
         return System.currentTimeMillis();
     }
 
+    private static void waitTillDumpstateExitedOrTimeout() throws Exception {
+        long startTimeMs = now();
+        while (isDumpstateRunning()) {
+            Thread.sleep(500 /* .5s */);
+            if (now() - startTimeMs >= DUMPSTATE_TEARDOWN_TIMEOUT_MS) {
+                break;
+            }
+            Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms for dumpstate to exit");
+        }
+    }
+
     private static void waitTillDumpstateRunningOrTimeout() throws Exception {
         long startTimeMs = now();
         while (!isDumpstateRunning()) {
@@ -500,6 +721,16 @@
         return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
     }
 
+    /*
+     * Returns a {@link BugreportParams} for full bugreport that reuses pre-dumped data.
+     *
+     * <p> This can take on the order of minutes to finish
+     */
+    private static BugreportParams fullWithUsePreDumpFlag() {
+        return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL,
+                BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+    }
+
     /* Allow/deny the consent dialog to sharing bugreport data or check existence only. */
     private enum ConsentReply {
         ALLOW,
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index f9f3b4c..0b8b29b 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -59,6 +59,7 @@
 import static org.mockito.Mockito.spy;
 
 import android.annotation.Nullable;
+import android.app.Notification.CallStyle;
 import android.content.Context;
 import android.content.Intent;
 import android.content.LocusId;
@@ -92,6 +93,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
@@ -531,6 +533,108 @@
     }
 
     @Test
+    public void testCallStyle_getSystemActions_forIncomingCall() {
+        PendingIntent answerIntent = createPendingIntent("answer");
+        PendingIntent declineIntent = createPendingIntent("decline");
+        Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+                new Person.Builder().setName("A Caller").build(),
+                declineIntent,
+                answerIntent
+        );
+        style.setBuilder(new Notification.Builder(mContext, "Channel"));
+
+        List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+        assertEquals(2, actions.size());
+        assertEquals(declineIntent, actions.get(0).actionIntent);
+        assertEquals(answerIntent, actions.get(1).actionIntent);
+    }
+
+    @Test
+    public void testCallStyle_getSystemActions_forOngoingCall() {
+        PendingIntent hangUpIntent = createPendingIntent("hangUp");
+        Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
+                new Person.Builder().setName("A Caller").build(),
+                hangUpIntent
+        );
+        style.setBuilder(new Notification.Builder(mContext, "Channel"));
+
+        List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+        assertEquals(1, actions.size());
+        assertEquals(hangUpIntent, actions.get(0).actionIntent);
+    }
+
+    @Test
+    public void testCallStyle_getSystemActions_forIncomingCallWithOtherActions() {
+        PendingIntent answerIntent = createPendingIntent("answer");
+        PendingIntent declineIntent = createPendingIntent("decline");
+        Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+                new Person.Builder().setName("A Caller").build(),
+                declineIntent,
+                answerIntent
+        );
+        Notification.Action actionToKeep = makeNotificationAction(null);
+        Notification.Action actionToDrop = makeNotificationAction(null);
+        Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
+                .addAction(actionToKeep)
+                .addAction(actionToDrop); //expect to move this action to the end
+        style.setBuilder(notifBuilder); //add a builder with actions
+
+        List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+        assertEquals(4, actions.size());
+        assertEquals(declineIntent, actions.get(0).actionIntent);
+        assertEquals(actionToKeep, actions.get(1));
+        assertEquals(answerIntent, actions.get(2).actionIntent);
+        assertEquals(actionToDrop, actions.get(3));
+    }
+
+    @Test
+    public void testCallStyle_getSystemActions_forOngoingCallWithOtherActions() {
+        PendingIntent hangUpIntent = createPendingIntent("hangUp");
+        Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
+                new Person.Builder().setName("A Caller").build(),
+                hangUpIntent
+        );
+        Notification.Action firstAction = makeNotificationAction(null);
+        Notification.Action secondAction = makeNotificationAction(null);
+        Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
+                .addAction(firstAction)
+                .addAction(secondAction);
+        style.setBuilder(notifBuilder); //add a builder with actions
+
+        List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+        assertEquals(3, actions.size());
+        assertEquals(hangUpIntent, actions.get(0).actionIntent);
+        assertEquals(firstAction, actions.get(1));
+        assertEquals(secondAction, actions.get(2));
+    }
+
+    @Test
+    public void testCallStyle_getSystemActions_dropsOldSystemActions() {
+        PendingIntent hangUpIntent = createPendingIntent("decline");
+        Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
+                new Person.Builder().setName("A Caller").build(),
+                hangUpIntent
+        );
+        Bundle actionExtras = new Bundle();
+        actionExtras.putBoolean("key_action_priority", true);
+        Notification.Action oldSystemAction = makeNotificationAction(
+                builder -> builder.addExtras(actionExtras)
+        );
+        Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
+                .addAction(oldSystemAction);
+        style.setBuilder(notifBuilder); //add a builder with actions
+
+        List<Notification.Action> actions = style.getActionsListWithSystemActions();
+
+        assertFalse("Old versions of system actions should be dropped.",
+                actions.contains(oldSystemAction));
+    }
+
+    @Test
     public void testBuild_ensureSmallIconIsNotTooBig_resizesIcon() {
         Icon hugeIcon = Icon.createWithBitmap(
                 Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
@@ -788,7 +892,7 @@
 
     @Test
     public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
-        Notification.Style style = new Notification.CallStyle();
+        Notification.Style style = new CallStyle();
         Bundle fakeTypes = new Bundle();
         fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
         fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
@@ -962,4 +1066,12 @@
         }
         return actionBuilder.build();
     }
+
+    /**
+     * Creates a PendingIntent with the given action.
+     */
+    private PendingIntent createPendingIntent(String action) {
+        return PendingIntent.getActivity(mContext, 0, new Intent(action),
+                PendingIntent.FLAG_MUTABLE);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 3768063..ed2b101 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -368,20 +368,4 @@
             PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
         assertEquals(n1, "cache_key.bluetooth.get_state");
     }
-
-    @Test
-    public void testOnTrimMemory() {
-        TestCache cache = new TestCache(MODULE, "trimMemoryTest");
-        // The cache is not active until it has been invalidated once.
-        cache.invalidateCache();
-        // Populate the cache with six entries.
-        for (int i = 0; i < 6; i++) {
-            cache.query(i);
-        }
-        // The maximum number of entries in TestCache is 4, so even though six entries were
-        // created, only four are retained.
-        assertEquals(4, cache.size());
-        PropertyInvalidatedCache.onTrimMemory();
-        assertEquals(0, cache.size());
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 591e347..215308d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -130,6 +130,10 @@
         if (!cropRect.intersect(mWholeAnimationBounds)) {
             // Hide the surface when it is outside of the animation area.
             t.setAlpha(mLeash, 0);
+        } else if (mAnimation.hasExtension()) {
+            // Allow the surface to be shown in its original bounds in case we want to use edge
+            // extensions.
+            cropRect.union(mChange.getEndAbsBounds());
         }
 
         // cropRect is in absolute coordinate, so we need to translate it to surface top left.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 756d802..490975c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -21,6 +21,7 @@
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 
 import android.animation.Animator;
@@ -45,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /** To run the ActivityEmbedding animations. */
 class ActivityEmbeddingAnimationRunner {
@@ -65,10 +67,31 @@
     void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction) {
+        // There may be some surface change that we want to apply after the start transaction is
+        // applied to make sure the surface is ready.
+        final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
+                new ArrayList<>();
         final Animator animator = createAnimator(info, startTransaction, finishTransaction,
-                () -> mController.onAnimationFinished(transition));
-        startTransaction.apply();
-        animator.start();
+                () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
+
+        // Start the animation.
+        if (!postStartTransactionCallbacks.isEmpty()) {
+            // postStartTransactionCallbacks require that the start transaction is already
+            // applied to run otherwise they may result in flickers and UI inconsistencies.
+            startTransaction.apply(true /* sync */);
+
+            // Run tasks that require startTransaction to already be applied
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
+                    postStartTransactionCallbacks) {
+                postStartTransactionCallback.accept(t);
+            }
+            t.apply();
+            animator.start();
+        } else {
+            startTransaction.apply();
+            animator.start();
+        }
     }
 
     /**
@@ -85,9 +108,13 @@
     Animator createAnimator(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Runnable animationFinishCallback) {
-        final List<ActivityEmbeddingAnimationAdapter> adapters =
-                createAnimationAdapters(info, startTransaction, finishTransaction);
+            @NonNull Runnable animationFinishCallback,
+            @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
+        final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
+                startTransaction);
+        addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
+                adapters);
+        addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
         long duration = 0;
         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
             duration = Math.max(duration, adapter.getDurationHint());
@@ -131,8 +158,7 @@
      */
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
         boolean isChangeTransition = false;
         for (TransitionInfo.Change change : info.getChanges()) {
             if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -148,25 +174,23 @@
             return createChangeAnimationAdapters(info, startTransaction);
         }
         if (Transitions.isClosingType(info.getType())) {
-            return createCloseAnimationAdapters(info, startTransaction, finishTransaction);
+            return createCloseAnimationAdapters(info);
         }
-        return createOpenAnimationAdapters(info, startTransaction, finishTransaction);
+        return createOpenAnimationAdapters(info);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction) {
-        return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
-                true /* isOpening */, mAnimationSpec::loadOpenAnimation);
+            @NonNull TransitionInfo info) {
+        return createOpenCloseAnimationAdapters(info, true /* isOpening */,
+                mAnimationSpec::loadOpenAnimation);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction) {
-        return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction,
-                false /* isOpening */, mAnimationSpec::loadCloseAnimation);
+            @NonNull TransitionInfo info) {
+        return createOpenCloseAnimationAdapters(info, false /* isOpening */,
+                mAnimationSpec::loadCloseAnimation);
     }
 
     /**
@@ -175,8 +199,7 @@
      */
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
-            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
+            @NonNull TransitionInfo info, boolean isOpening,
             @NonNull AnimationProvider animationProvider) {
         // We need to know if the change window is only a partial of the whole animation screen.
         // If so, we will need to adjust it to make the whole animation screen looks like one.
@@ -200,8 +223,7 @@
         final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
         for (TransitionInfo.Change change : openingChanges) {
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
-                    info, change, startTransaction, finishTransaction, animationProvider,
-                    openingWholeScreenBounds);
+                    info, change, animationProvider, openingWholeScreenBounds);
             if (isOpening) {
                 adapter.overrideLayer(offsetLayer++);
             }
@@ -209,8 +231,7 @@
         }
         for (TransitionInfo.Change change : closingChanges) {
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
-                    info, change, startTransaction, finishTransaction, animationProvider,
-                    closingWholeScreenBounds);
+                    info, change, animationProvider, closingWholeScreenBounds);
             if (!isOpening) {
                 adapter.overrideLayer(offsetLayer++);
             }
@@ -219,20 +240,51 @@
         return adapters;
     }
 
+    /** Adds edge extension to the surfaces that have such an animation property. */
+    private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks,
+            @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+        for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+            final Animation animation = adapter.mAnimation;
+            if (!animation.hasExtension()) {
+                continue;
+            }
+            final TransitionInfo.Change change = adapter.mChange;
+            if (Transitions.isOpeningType(adapter.mChange.getMode())) {
+                // Need to screenshot after startTransaction is applied otherwise activity
+                // may not be visible or ready yet.
+                postStartTransactionCallbacks.add(
+                        t -> edgeExtendWindow(change, animation, t, finishTransaction));
+            } else {
+                // Can screenshot now (before startTransaction is applied)
+                edgeExtendWindow(change, animation, startTransaction, finishTransaction);
+            }
+        }
+    }
+
+    /** Adds background color to the transition if any animation has such a property. */
+    private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
+        for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+            final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange,
+                    adapter.mAnimation, 0 /* defaultColor */);
+            if (backgroundColor != 0) {
+                // We only need to show one color.
+                addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
+                        finishTransaction);
+                return;
+            }
+        }
+    }
+
     @NonNull
     private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
             @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
         final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
-        // We may want to show a background color for open/close transition.
-        final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
-                0 /* defaultColor */);
-        if (backgroundColor != 0) {
-            addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
-                    finishTransaction);
-        }
         return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
                 wholeAnimationBounds);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index eb6ac76..58b2366 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -181,15 +181,15 @@
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = Transitions.isOpeningType(change.getMode());
         final Animation animation;
-        // TODO(b/207070762): Implement edgeExtension version
         if (shouldShowBackdrop(info, change)) {
             animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
                     ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
                     : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
         } else {
+            // Use the same edge extension animation as regular activity open.
             animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                    ? com.android.internal.R.anim.task_fragment_open_enter
-                    : com.android.internal.R.anim.task_fragment_open_exit);
+                    ? com.android.internal.R.anim.activity_open_enter
+                    : com.android.internal.R.anim.activity_open_exit);
         }
         // Use the whole animation bounds instead of the change bounds, so that when multiple change
         // targets are opening at the same time, the animation applied to each will be the same.
@@ -205,15 +205,15 @@
             @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
         final boolean isEnter = Transitions.isOpeningType(change.getMode());
         final Animation animation;
-        // TODO(b/207070762): Implement edgeExtension version
         if (shouldShowBackdrop(info, change)) {
             animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
                     ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
                     : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
         } else {
+            // Use the same edge extension animation as regular activity close.
             animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
-                    ? com.android.internal.R.anim.task_fragment_close_enter
-                    : com.android.internal.R.anim.task_fragment_close_exit);
+                    ? com.android.internal.R.anim.activity_close_enter
+                    : com.android.internal.R.anim.activity_close_exit);
         }
         // Use the whole animation bounds instead of the change bounds, so that when multiple change
         // targets are closing at the same time, the animation applied to each will be the same.
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 91c153e..9c2c2fa 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
@@ -59,6 +59,7 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
@@ -76,10 +77,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Canvas;
 import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.view.Choreographer;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
@@ -97,7 +94,6 @@
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
-import android.window.ScreenCapture;
 import android.window.TransitionInfo;
 import android.window.TransitionMetrics;
 import android.window.TransitionRequestInfo;
@@ -526,123 +522,6 @@
         }
     }
 
-    private void edgeExtendWindow(TransitionInfo.Change change,
-            Animation a, SurfaceControl.Transaction startTransaction,
-            SurfaceControl.Transaction finishTransaction) {
-        // Do not create edge extension surface for transfer starting window change.
-        // The app surface could be empty thus nothing can draw on the hardware renderer, which will
-        // block this thread when calling Surface#unlockCanvasAndPost.
-        if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
-            return;
-        }
-        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 = Math.max(change.getStartAbsBounds().height(),
-                change.getEndAbsBounds().height());
-        final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
-                change.getEndAbsBounds().width());
-        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;
-            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
-                    "Left Edge Extension", startTransaction, finishTransaction);
-        }
-
-        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;
-            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
-                    "Top Edge Extension", startTransaction, finishTransaction);
-        }
-
-        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;
-            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
-                    "Right Edge Extension", startTransaction, finishTransaction);
-        }
-
-        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;
-            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
-                    "Bottom Edge Extension", startTransaction, finishTransaction);
-        }
-    }
-
-    private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
-            Rect extensionRect, int xPos, int yPos, String layerName,
-            SurfaceControl.Transaction startTransaction,
-            SurfaceControl.Transaction finishTransaction) {
-        final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
-                .setName(layerName)
-                .setParent(surfaceToExtend)
-                .setHidden(true)
-                .setCallsite("DefaultTransitionHandler#startAnimation")
-                .setOpaque(true)
-                .setBufferSize(extensionRect.width(), extensionRect.height())
-                .build();
-
-        ScreenCapture.LayerCaptureArgs captureArgs =
-                new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
-                        .setSourceCrop(edgeBounds)
-                        .setFrameScale(1)
-                        .setPixelFormat(PixelFormat.RGBA_8888)
-                        .setChildrenOnly(true)
-                        .setAllowProtected(true)
-                        .build();
-        final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
-                ScreenCapture.captureLayers(captureArgs);
-
-        if (edgeBuffer == null) {
-            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                    "Failed to capture edge of window.");
-            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);
-        finishTransaction.remove(edgeExtensionLayer);
-
-        return edgeExtensionLayer;
-    }
-
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index efee6f40..e338221 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
@@ -34,10 +35,20 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Shader;
 import android.os.SystemProperties;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.window.ScreenCapture;
 import android.window.TransitionInfo;
 
 import com.android.internal.R;
@@ -217,4 +228,126 @@
                 .show(animationBackgroundSurface);
         finishTransaction.remove(animationBackgroundSurface);
     }
+
+    /**
+     * Adds edge extension surface to the given {@code change} for edge extension animation.
+     */
+    public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
+            @NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        // Do not create edge extension surface for transfer starting window change.
+        // The app surface could be empty thus nothing can draw on the hardware renderer, which will
+        // block this thread when calling Surface#unlockCanvasAndPost.
+        if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+            return;
+        }
+        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 = Math.max(change.getStartAbsBounds().height(),
+                change.getEndAbsBounds().height());
+        final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(),
+                change.getEndAbsBounds().width());
+        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;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Left Edge Extension", startTransaction, finishTransaction);
+        }
+
+        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;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Top Edge Extension", startTransaction, finishTransaction);
+        }
+
+        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;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Right Edge Extension", startTransaction, finishTransaction);
+        }
+
+        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;
+            createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos,
+                    "Bottom Edge Extension", startTransaction, finishTransaction);
+        }
+    }
+
+    /**
+     * Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
+     * animation.
+     */
+    private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
+            @NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
+            @NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction) {
+        final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
+                .setName(layerName)
+                .setParent(surfaceToExtend)
+                .setHidden(true)
+                .setCallsite("TransitionAnimationHelper#createExtensionSurface")
+                .setOpaque(true)
+                .setBufferSize(extensionRect.width(), extensionRect.height())
+                .build();
+
+        final ScreenCapture.LayerCaptureArgs captureArgs =
+                new ScreenCapture.LayerCaptureArgs.Builder(surfaceToExtend)
+                        .setSourceCrop(edgeBounds)
+                        .setFrameScale(1)
+                        .setPixelFormat(PixelFormat.RGBA_8888)
+                        .setChildrenOnly(true)
+                        .setAllowProtected(true)
+                        .build();
+        final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
+                ScreenCapture.captureLayers(captureArgs);
+
+        if (edgeBuffer == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                    "Failed to capture edge of window.");
+            return null;
+        }
+
+        final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
+                Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+        final Paint paint = new Paint();
+        paint.setShader(shader);
+
+        final Surface surface = new Surface(edgeExtensionLayer);
+        final 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);
+        finishTransaction.remove(edgeExtensionLayer);
+
+        return edgeExtensionLayer;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 7d498dc..1fc0375 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -92,7 +92,7 @@
     }
 
     /** Checks that [pipApp] window is animated towards default position in right bottom corner */
-    @FlakyTest(bugId = 251135384)
+    @Presubmit
     @Test
     fun pipLayerMovesTowardsRightBottomCorner() {
         // in gestural nav the swipe makes PiP first go upwards
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 813ac5d..84a8c0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -26,7 +26,7 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isRotated
+import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -79,10 +79,18 @@
                 secondaryApp.windowMatchesAnyOf(window)
             } ?: return@add false
 
-            if (testSpec.startRotation.isRotated()) {
-                return@add primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+            if (isLandscape(testSpec.endRotation)) {
+                return@add if (testSpec.isTablet) {
+                    secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                } else {
+                    primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                }
             } else {
-                return@add primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                return@add if (testSpec.isTablet) {
+                    primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                } else {
+                    primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                }
             }
         }.waitForAndVerify()
     }
@@ -101,14 +109,27 @@
             val secondaryVisibleRegion = secondaryAppLayer.visibleRegion?.bounds
                 ?: return@add false
 
-            if (testSpec.startRotation.isRotated()) {
-                return@add primaryVisibleRegion.right <= secondaryVisibleRegion.left
+            if (isLandscape(testSpec.endRotation)) {
+                return@add if (testSpec.isTablet) {
+                    secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                } else {
+                    primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                }
             } else {
-                return@add primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                return@add if (testSpec.isTablet) {
+                    primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                } else {
+                    primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                }
             }
         }.waitForAndVerify()
     }
 
+    private fun isLandscape(rotation: Int): Boolean {
+        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+        return displayBounds.width > displayBounds.height
+    }
+
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 98b5912..79070b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -40,6 +40,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+
 /**
  * Tests for {@link ActivityEmbeddingAnimationRunner}.
  *
@@ -62,13 +64,13 @@
         final TransitionInfo.Change embeddingChange = createChange();
         embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
         info.addChange(embeddingChange);
-        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
 
         mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
 
         final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
         verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
-                finishCallback.capture());
+                finishCallback.capture(), any());
         verify(mStartTransaction).apply();
         verify(mAnimator).start();
         verifyNoMoreInteractions(mFinishTransaction);
@@ -88,7 +90,8 @@
         info.addChange(embeddingChange);
         final Animator animator = mAnimRunner.createAnimator(
                 info, mStartTransaction, mFinishTransaction,
-                () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+                () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
+                new ArrayList());
 
         // The animation should be empty when it is behind starting window.
         assertEquals(0, animator.getDuration());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 3792e83..54a12ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -56,13 +56,12 @@
     @Mock
     SurfaceControl.Transaction mFinishTransaction;
     @Mock
-    Transitions.TransitionFinishCallback mFinishCallback;
-    @Mock
     Animator mAnimator;
 
     ActivityEmbeddingController mController;
     ActivityEmbeddingAnimationRunner mAnimRunner;
     ActivityEmbeddingAnimationSpec mAnimSpec;
+    Transitions.TransitionFinishCallback mFinishCallback;
 
     @CallSuper
     @Before
@@ -75,9 +74,11 @@
         assertNotNull(mAnimRunner);
         mAnimSpec = mAnimRunner.mAnimationSpec;
         assertNotNull(mAnimSpec);
+        mFinishCallback = (wct, wctCB) -> {};
         spyOn(mController);
         spyOn(mAnimRunner);
         spyOn(mAnimSpec);
+        spyOn(mFinishCallback);
     }
 
     /** Creates a mock {@link TransitionInfo.Change}. */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index baecf6f..4d98b6b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -55,7 +55,7 @@
     @Before
     public void setup() {
         super.setUp();
-        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any());
+        doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
     }
 
     @Test
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 6aead43..08655ca 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -87,6 +87,13 @@
      * parameter is an instance of {@link java.lang.Integer}.
      */
     public static final int RULE_MATCH_USERID = 0x1 << 3;
+    /**
+     * A rule requiring the audio session id of the audio stream to match that specified.
+     * This mixing rule can be added with {@link Builder#addMixRule(int, Object)} where Object
+     * parameter is an instance of {@link java.lang.Integer}.
+     * @see android.media.AudioTrack.Builder#setSessionId
+     */
+    public static final int RULE_MATCH_AUDIO_SESSION_ID = 0x1 << 4;
 
     private final static int RULE_EXCLUSION_MASK = 0x8000;
     /**
@@ -115,6 +122,13 @@
     public static final int RULE_EXCLUDE_USERID =
             RULE_EXCLUSION_MASK | RULE_MATCH_USERID;
 
+    /**
+     * @hide
+     * A rule requiring the audio session id information to differ.
+     */
+    public static final int RULE_EXCLUDE_AUDIO_SESSION_ID =
+            RULE_EXCLUSION_MASK | RULE_MATCH_AUDIO_SESSION_ID;
+
     /** @hide */
     public static final class AudioMixMatchCriterion {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -166,6 +180,7 @@
                     break;
                 case RULE_MATCH_UID:
                 case RULE_MATCH_USERID:
+                case RULE_MATCH_AUDIO_SESSION_ID:
                     dest.writeInt(mIntProp);
                     break;
                 default:
@@ -315,6 +330,7 @@
             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
             case RULE_MATCH_UID:
             case RULE_MATCH_USERID:
+            case RULE_MATCH_AUDIO_SESSION_ID:
                 return true;
             default:
                 return false;
@@ -338,6 +354,7 @@
             case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
             case RULE_MATCH_UID:
             case RULE_MATCH_USERID:
+            case RULE_MATCH_AUDIO_SESSION_ID:
                 return true;
             default:
                 return false;
@@ -445,7 +462,8 @@
          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
          *     {@link AudioMixingRule#RULE_MATCH_UID} or
-         *     {@link AudioMixingRule#RULE_MATCH_USERID}.
+         *     {@link AudioMixingRule#RULE_MATCH_USERID} or
+         *     {@link AudioMixingRule#RULE_MATCH_AUDIO_SESSION_ID}.
          * @param property see the definition of each rule for the type to use (either an
          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
          * @return the same Builder instance.
@@ -476,7 +494,8 @@
          * @param rule one of {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
          *     {@link AudioMixingRule#RULE_MATCH_UID} or
-         *     {@link AudioMixingRule#RULE_MATCH_USERID}.
+         *     {@link AudioMixingRule#RULE_MATCH_USERID} or
+         *     {@link AudioMixingRule#RULE_MATCH_AUDIO_SESSION_ID}.
          * @param property see the definition of each rule for the type to use (either an
          *     {@link AudioAttributes} or an {@link java.lang.Integer}).
          * @return the same Builder instance.
@@ -606,9 +625,12 @@
          * @param intProp an integer property to match or exclude, null if not used.
          * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
          *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
-         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET},
          *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET},
-         *     {@link AudioMixingRule#RULE_MATCH_UID}, {@link AudioMixingRule#RULE_EXCLUDE_UID}.
+         *     {@link AudioMixingRule#RULE_MATCH_UID},
+         *     {@link AudioMixingRule#RULE_EXCLUDE_UID},
+         *     {@link AudioMixingRule#RULE_MATCH_AUDIO_SESSION_ID},
+         *     {@link AudioMixingRule#RULE_EXCLUDE_AUDIO_SESSION_ID}
          *     {@link AudioMixingRule#RULE_MATCH_USERID},
          *     {@link AudioMixingRule#RULE_EXCLUDE_USERID}.
          * @return the same Builder instance.
@@ -645,6 +667,7 @@
                         break;
                     case RULE_MATCH_UID:
                     case RULE_MATCH_USERID:
+                    case RULE_MATCH_AUDIO_SESSION_ID:
                         mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
                         break;
                     default:
@@ -666,6 +689,7 @@
                     break;
                 case RULE_MATCH_UID:
                 case RULE_MATCH_USERID:
+                case RULE_MATCH_AUDIO_SESSION_ID:
                     intProp = new Integer(in.readInt());
                     break;
                 default:
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index f3731b6..440447e 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -217,6 +217,14 @@
                         textDump += "  exclude userId ";
                         textDump += criterion.mIntProp;
                         break;
+                    case AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID:
+                        textDump += " match audio session id";
+                        textDump += criterion.mIntProp;
+                        break;
+                    case AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID:
+                        textDump += " exclude audio session id ";
+                        textDump += criterion.mIntProp;
+                        break;
                     default:
                         textDump += "invalid rule!";
                 }
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
index ad7ab97..a83e7d3 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
@@ -22,9 +22,11 @@
 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET;
 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_AUDIO_SESSION_ID;
 import static android.media.audiopolicy.AudioMixingRule.RULE_EXCLUDE_UID;
 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -61,12 +63,14 @@
             new AudioAttributes.Builder().setCapturePreset(VOICE_RECOGNITION).build();
     private static final int TEST_UID = 42;
     private static final int OTHER_UID = 77;
+    private static final int TEST_SESSION_ID = 1234;
 
     @Test
     public void testConstructValidRule() {
         AudioMixingRule rule = new AudioMixingRule.Builder()
                 .addMixRule(RULE_MATCH_ATTRIBUTE_USAGE, USAGE_MEDIA_AUDIO_ATTRIBUTES)
                 .addMixRule(RULE_MATCH_UID, TEST_UID)
+                .excludeMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
                 .build();
 
         // Based on the rules, the mix type should fall back to MIX_ROLE_PLAYERS,
@@ -74,7 +78,8 @@
         assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
         assertThat(rule.getCriteria(), containsInAnyOrder(
                 isAudioMixMatchUsageCriterion(USAGE_MEDIA),
-                isAudioMixMatchUidCriterion(TEST_UID)));
+                isAudioMixMatchUidCriterion(TEST_UID),
+                isAudioMixExcludeSessionCriterion(TEST_SESSION_ID)));
     }
 
     @Test
@@ -183,6 +188,30 @@
                         .build());
     }
 
+    @Test
+    public void sessionIdRuleCompatibleWithPlayersMix() {
+        int sessionId = 42;
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, sessionId)
+                .setTargetMixRole(MIX_ROLE_PLAYERS)
+                .build();
+
+        assertEquals(rule.getTargetMixRole(), MIX_ROLE_PLAYERS);
+        assertThat(rule.getCriteria(), containsInAnyOrder(isAudioMixSessionCriterion(sessionId)));
+    }
+
+    @Test
+    public void sessionIdRuleCompatibleWithInjectorMix() {
+        AudioMixingRule rule = new AudioMixingRule.Builder()
+                .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID)
+                .setTargetMixRole(MIX_ROLE_INJECTOR)
+                .build();
+
+        assertEquals(rule.getTargetMixRole(), MIX_ROLE_INJECTOR);
+        assertThat(rule.getCriteria(),
+                containsInAnyOrder(isAudioMixSessionCriterion(TEST_SESSION_ID)));
+    }
+
 
     private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
         return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {
@@ -257,5 +286,31 @@
         return isAudioMixUsageCriterion(usage, /*exclude=*/ false);
     }
 
+    private static Matcher isAudioMixSessionCriterion(int sessionId, boolean exclude) {
+        return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("sessionId mix criterion") {
+            @Override
+            public boolean matchesSafely(AudioMixMatchCriterion item) {
+                int excludeRule =
+                        exclude ? RULE_EXCLUDE_AUDIO_SESSION_ID : RULE_MATCH_AUDIO_SESSION_ID;
+                return item.getRule() == excludeRule && item.getIntProp() == sessionId;
+            }
+
+            @Override
+            public void describeMismatchSafely(
+                    AudioMixMatchCriterion item, Description mismatchDescription) {
+                mismatchDescription.appendText(
+                        String.format("is not %s criterion with session id %d",
+                        exclude ? "exclude" : "match", sessionId));
+            }
+        };
+    }
+
+    private static Matcher isAudioMixSessionCriterion(int sessionId) {
+        return isAudioMixSessionCriterion(sessionId, /*exclude=*/ false);
+    }
+
+    private static Matcher isAudioMixExcludeSessionCriterion(int sessionId) {
+        return isAudioMixSessionCriterion(sessionId, /*exclude=*/ true);
+    }
 
 }
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
index a207bf1..fa186c3 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
@@ -304,13 +304,13 @@
                 for (int j = 0; j < NUM_EFFECTS; j++) {
                     effects[j] = new BassBoost(0, mSession);
                     effects[j].setControlStatusListener(mEffectListener);
-                    yield();
+                    this.yield();
                 }
                 for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
                     Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
                     effects[j].release();
                     effects[j] = null;
-                    yield();
+                    this.yield();
                 }
             }
             Log.w(TAG, "HammerReleaseTest ended");
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
index dcfe11a..5d91a2e 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
@@ -251,13 +251,13 @@
             for (int i = 0; i < NUM_ITERATIONS; i++) {
                 for (int j = 0; j < NUM_EFFECTS; j++) {
                     effects[j] = new Visualizer(mSession);
-                    yield();
+                    this.yield();
                 }
                 for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
                     Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
                     effects[j].release();
                     effects[j] = null;
-                    yield();
+                    this.yield();
                 }
             }
             Log.w(TAG, "HammerReleaseTest ended");
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index db54ae3..d4439f9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -39,7 +39,6 @@
         android:layout_height="wrap_content"
         android:paddingStart="24dp"
         android:paddingEnd="24dp"
-        android:singleLine="true"
         android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
new file mode 100644
index 0000000..96cc02f
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class CredentialEntryUi(
+  val userName: CharSequence,
+  val displayName: CharSequence?,
+  val icon: Icon?,
+  // TODO: add last used.
+) {
+  companion object {
+    fun fromSlice(slice: Slice): CredentialEntryUi {
+      val items = slice.items
+
+      var title: String? = null
+      var subTitle: String? = null
+      var icon: Icon? = null
+
+      items.forEach {
+        if (it.hasHint(Entry.HINT_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(Entry.HINT_SUBTITLE)) {
+          subTitle = it.text.toString()
+        } else if (it.hasHint(Entry.HINT_TITLE)) {
+          title = it.text.toString()
+        }
+      }
+      // TODO: fail NPE more elegantly.
+      return CredentialEntryUi(title!!, subTitle, icon)
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 46bf19c..8db547a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager
 
 import android.app.slice.Slice
@@ -9,11 +25,8 @@
 import android.credentials.ui.RequestInfo
 import android.graphics.drawable.Icon
 import android.os.Binder
-import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.createflow.CreatePasskeyUiState
 import com.android.credentialmanager.createflow.CreateScreenState
-import com.android.credentialmanager.createflow.ProviderInfo
-import com.android.credentialmanager.getflow.CredentialOptionInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
 
@@ -41,6 +54,39 @@
     ) ?: testProviderList()
   }
 
+  fun getCredentialInitialUiState(): GetCredentialUiState {
+    val providerList = GetFlowUtils.toProviderList(providerList, context)
+    return GetCredentialUiState(
+      providerList,
+      GetScreenState.CREDENTIAL_SELECTION,
+      providerList.first()
+    )
+  }
+
+  fun createPasskeyInitialUiState(): CreatePasskeyUiState {
+    val providerList = CreateFlowUtils.toProviderList(providerList, context)
+    return CreatePasskeyUiState(
+      providers = providerList,
+      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+    )
+  }
+
+  companion object {
+    lateinit var repo: CredentialManagerRepo
+
+    fun setup(
+      context: Context,
+      intent: Intent,
+    ) {
+      repo = CredentialManagerRepo(context, intent)
+    }
+
+    fun getInstance(): CredentialManagerRepo {
+      return repo
+    }
+  }
+
+  // TODO: below are prototype functionalities. To be removed for productionization.
   private fun testProviderList(): List<ProviderData> {
     return listOf(
       ProviderData(
@@ -94,143 +140,4 @@
       slice
     )
   }
-
-  private fun getCredentialProviderList():
-    List<com.android.credentialmanager.getflow.ProviderInfo> {
-      return listOf(
-        com.android.credentialmanager.getflow.ProviderInfo(
-          icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = "Google Password Manager",
-          appDomainName = "tribank.us",
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          credentialOptions = listOf(
-            CredentialOptionInfo(
-              icon = context.getDrawable(R.drawable.ic_passkey)!!,
-              title = "Elisa Backett",
-              subtitle = "elisa.beckett@gmail.com",
-              id = "id-1",
-            ),
-            CredentialOptionInfo(
-              icon = context.getDrawable(R.drawable.ic_passkey)!!,
-              title = "Elisa Backett Work",
-              subtitle = "elisa.beckett.work@google.com",
-              id = "id-2",
-            ),
-          )
-        ),
-        com.android.credentialmanager.getflow.ProviderInfo(
-          icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = "Lastpass",
-          appDomainName = "tribank.us",
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          credentialOptions = listOf(
-            CredentialOptionInfo(
-              icon = context.getDrawable(R.drawable.ic_passkey)!!,
-              title = "Elisa Backett",
-              subtitle = "elisa.beckett@lastpass.com",
-              id = "id-1",
-            ),
-          )
-        ),
-        com.android.credentialmanager.getflow.ProviderInfo(
-          icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = "Dashlane",
-          appDomainName = "tribank.us",
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          credentialOptions = listOf(
-            CredentialOptionInfo(
-              icon = context.getDrawable(R.drawable.ic_passkey)!!,
-              title = "Elisa Backett",
-              subtitle = "elisa.beckett@dashlane.com",
-              id = "id-1",
-            ),
-          )
-        ),
-      )
-  }
-
-  private fun createCredentialProviderList(): List<ProviderInfo> {
-    return listOf(
-      ProviderInfo(
-        icon = context.getDrawable(R.drawable.ic_passkey)!!,
-        name = "Google Password Manager",
-        appDomainName = "tribank.us",
-        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-        createOptions = listOf(
-          CreateOptionInfo(
-            icon = context.getDrawable(R.drawable.ic_passkey)!!,
-            title = "Elisa Backett",
-            subtitle = "elisa.beckett@gmail.com",
-            id = "id-1",
-          ),
-          CreateOptionInfo(
-            icon = context.getDrawable(R.drawable.ic_passkey)!!,
-            title = "Elisa Backett Work",
-            subtitle = "elisa.beckett.work@google.com",
-            id = "id-2",
-          ),
-        )
-      ),
-      ProviderInfo(
-        icon = context.getDrawable(R.drawable.ic_passkey)!!,
-        name = "Lastpass",
-        appDomainName = "tribank.us",
-        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-        createOptions = listOf(
-          CreateOptionInfo(
-            icon = context.getDrawable(R.drawable.ic_passkey)!!,
-            title = "Elisa Backett",
-            subtitle = "elisa.beckett@lastpass.com",
-            id = "id-1",
-          ),
-        )
-      ),
-      ProviderInfo(
-        icon = context.getDrawable(R.drawable.ic_passkey)!!,
-        name = "Dashlane",
-        appDomainName = "tribank.us",
-        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-        createOptions = listOf(
-          CreateOptionInfo(
-            icon = context.getDrawable(R.drawable.ic_passkey)!!,
-            title = "Elisa Backett",
-            subtitle = "elisa.beckett@dashlane.com",
-            id = "id-1",
-          ),
-        )
-      ),
-    )
-  }
-
-  fun getCredentialInitialUiState(): GetCredentialUiState {
-    val providerList = getCredentialProviderList()
-    return GetCredentialUiState(
-      providerList,
-      GetScreenState.CREDENTIAL_SELECTION,
-      providerList.first()
-    )
-  }
-
-  fun createPasskeyInitialUiState(): CreatePasskeyUiState {
-    val providerList = createCredentialProviderList()
-    return CreatePasskeyUiState(
-      providers = providerList,
-      currentScreenState = CreateScreenState.PASSKEY_INTRO,
-    )
-  }
-
-  companion object {
-    lateinit var repo: CredentialManagerRepo
-
-    fun setup(
-      context: Context,
-      intent: Intent,
-    ) {
-      repo = CredentialManagerRepo(context, intent)
-    }
-
-    fun getInstance(): CredentialManagerRepo {
-      return repo
-    }
-  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 98c824c..b538ae7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -1,5 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager
 
+import android.credentials.ui.RequestInfo
 import android.os.Bundle
 import android.util.Log
 import androidx.activity.ComponentActivity
@@ -16,14 +33,20 @@
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     CredentialManagerRepo.setup(this, intent)
-    val startDestination = intent.extras?.getString(
-      "start_destination",
-      "CREATE_PASSKEY"
-    ) ?: "CREATE_PASSKEY"
-
-    setContent {
-      CredentialSelectorTheme {
-        CredentialManagerBottomSheet(startDestination)
+    val requestInfo = intent.extras?.getParcelable<RequestInfo>(RequestInfo.EXTRA_REQUEST_INFO)
+    if (requestInfo != null) {
+      val requestType = requestInfo.type
+      setContent {
+        CredentialSelectorTheme {
+          CredentialManagerBottomSheet(requestType)
+        }
+      }
+    } else {
+      // TODO: prototype only code to be removed. In production should exit.
+      setContent {
+        CredentialSelectorTheme {
+          CredentialManagerBottomSheet(RequestInfo.TYPE_CREATE)
+        }
       }
     }
   }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
new file mode 100644
index 0000000..7159ab9
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.Context
+import android.credentials.ui.Entry
+import android.credentials.ui.ProviderData
+import com.android.credentialmanager.createflow.CreateOptionInfo
+import com.android.credentialmanager.getflow.CredentialOptionInfo
+import com.android.credentialmanager.getflow.ProviderInfo
+
+/** Utility functions for converting CredentialManager data structures to or from UI formats. */
+class GetFlowUtils {
+  companion object {
+
+    fun toProviderList(
+      providerDataList: List<ProviderData>,
+      context: Context,
+    ): List<ProviderInfo> {
+      return providerDataList.map {
+        ProviderInfo(
+          // TODO: replace to extract from the service data structure when available
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = it.packageName,
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
+        )
+      }
+    }
+
+
+    /* From service data structure to UI credential entry list representation. */
+    private fun toCredentialOptionInfoList(
+      credentialEntries: List<Entry>,
+      context: Context,
+    ): List<CredentialOptionInfo> {
+      return credentialEntries.map {
+        val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)
+
+        // Consider directly move the UI object into the class.
+        return@map CredentialOptionInfo(
+          // TODO: remove fallbacks
+          icon = credentialEntryUi.icon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          title = credentialEntryUi.userName.toString(),
+          subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
+          id = it.entryId,
+        )
+      }
+    }
+  }
+}
+
+class CreateFlowUtils {
+  companion object {
+
+    fun toProviderList(
+      providerDataList: List<ProviderData>,
+      context: Context,
+    ): List<com.android.credentialmanager.createflow.ProviderInfo> {
+      return providerDataList.map {
+        com.android.credentialmanager.createflow.ProviderInfo(
+          // TODO: replace to extract from the service data structure when available
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = it.packageName,
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          createOptions = toCreationOptionInfoList(it.credentialEntries, context),
+        )
+      }
+    }
+
+    private fun toCreationOptionInfoList(
+      creationEntries: List<Entry>,
+      context: Context,
+    ): List<CreateOptionInfo> {
+      return creationEntries.map {
+        val saveEntryUi = SaveEntryUi.fromSlice(it.slice)
+
+        return@map CreateOptionInfo(
+          // TODO: remove fallbacks
+          icon = saveEntryUi.icon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          title = saveEntryUi.title.toString(),
+          subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
+          id = it.entryId,
+        )
+      }
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
new file mode 100644
index 0000000..2b63e1d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a save entry used during the create credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class SaveEntryUi(
+  val title: CharSequence,
+  val subTitle: CharSequence?,
+  val icon: Icon?,
+  // TODO: add
+) {
+  companion object {
+    fun fromSlice(slice: Slice): SaveEntryUi {
+      val items = slice.items
+
+      var title: String? = null
+      var subTitle: String? = null
+      var icon: Icon? = null
+
+      items.forEach {
+        if (it.hasHint(Entry.HINT_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(Entry.HINT_SUBTITLE)) {
+          subTitle = it.text.toString()
+        } else if (it.hasHint(Entry.HINT_TITLE)) {
+          title = it.text.toString()
+        }
+      }
+      // TODO: fail NPE more elegantly.
+      return SaveEntryUi(title!!, subTitle, icon)
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
index 8bb80a1..b5e9fd00 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/DialogType.kt
@@ -1,5 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.common
 
+import android.credentials.ui.RequestInfo
+
 enum class DialogType {
   CREATE_PASSKEY,
   GET_CREDENTIALS,
@@ -8,10 +26,10 @@
 
   companion object {
     fun toDialogType(value: String): DialogType {
-      return try {
-        valueOf(value)
-      } catch (e: IllegalArgumentException) {
-        UNKNOWN
+      return when (value) {
+        RequestInfo.TYPE_GET -> GET_CREDENTIALS
+        RequestInfo.TYPE_CREATE -> CREATE_PASSKEY
+        else -> UNKNOWN
       }
     }
   }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 044688b..12f1846 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.createflow
 
 import android.graphics.drawable.Drawable
@@ -14,7 +30,7 @@
   val icon: Drawable,
   val title: String,
   val subtitle: String,
-  val id: String,
+  val id: Int,
 )
 
 /** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 997519d..b61f465 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -222,7 +222,9 @@
   ) {
     Column() {
       TopAppBar(
-        title = { Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1) },
+        title = {
+          Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1)
+        },
         backgroundColor = lightBackgroundColor,
         elevation = 0.dp,
         navigationIcon =
@@ -343,7 +345,7 @@
 @Composable
 fun CreationSelectionCard(
   providerInfo: ProviderInfo,
-  onOptionSelected: (String) -> Unit,
+  onOptionSelected: (Int) -> Unit,
   onCancel: () -> Unit,
   multiProvider: Boolean,
   onMoreOptionSelected: (String) -> Unit,
@@ -414,7 +416,7 @@
 
 @ExperimentalMaterialApi
 @Composable
-fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (String) -> Unit) {
+fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (Int) -> Unit) {
   Chip(
     modifier = Modifier.fillMaxWidth(),
     onClick = {onOptionSelected(createOptionInfo.id)},
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 15300de..5b70f9d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.createflow
 
 import android.util.Log
@@ -42,7 +58,7 @@
     )
   }
 
-  fun onCreateOptionSelected(createOptionId: String) {
+  fun onCreateOptionSelected(createOptionId: Int) {
     Log.d("Account Selector", "Option selected for creation: $createOptionId")
   }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 1ca70ed..0b18822 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.getflow
 
 import androidx.compose.foundation.Image
@@ -74,7 +90,7 @@
 @Composable
 fun CredentialSelectionCard(
   providerInfo: ProviderInfo,
-  onOptionSelected: (String) -> Unit,
+  onOptionSelected: (Int) -> Unit,
   onCancel: () -> Unit,
   multiProvider: Boolean,
   onMoreOptionSelected: () -> Unit,
@@ -149,7 +165,7 @@
 @Composable
 fun CredentialOptionRow(
     credentialOptionInfo: CredentialOptionInfo,
-    onOptionSelected: (String) -> Unit
+    onOptionSelected: (Int) -> Unit
 ) {
   Chip(
     modifier = Modifier.fillMaxWidth(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 06bcd7f..0fdd8ec 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.getflow
 
 import android.util.Log
@@ -20,7 +36,7 @@
   var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())
       private set
 
-  fun onCredentailSelected(credentialId: String) {
+  fun onCredentailSelected(credentialId: Int) {
     Log.d("Account Selector", "credential selected: $credentialId")
   }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 867e9c2..acea8c9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.credentialmanager.getflow
 
 import android.graphics.drawable.Drawable
@@ -14,7 +30,7 @@
   val icon: Drawable,
   val title: String,
   val subtitle: String,
-  val id: String,
+  val id: Int,
 )
 
 /** The name of the current screen. */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index f9d2dbc..476dd30 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -47,13 +47,19 @@
 
 /**
  * The Activity to render ALL SPA pages, and handles jumps between SPA pages.
+ *
  * One can open any SPA page by:
- *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination <SpaPageRoute>
- * For gallery, BrowseActivityComponent = com.android.settingslib.spa.gallery/.MainActivity
- * For SettingsGoogle, BrowseActivityComponent = com.android.settings/.spa.SpaActivity
+ * ```
+ * $ adb shell am start -n <BrowseActivityComponent> -e spaActivityDestination <SpaPageRoute>
+ * ```
+ * - For Gallery, BrowseActivityComponent = com.android.settingslib.spa.gallery/.GalleryMainActivity
+ * - For Settings, BrowseActivityComponent = com.android.settings/.spa.SpaActivity
+ *
  * Some examples:
- *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination HOME
- *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination ARGUMENT/bar/5
+ * ```
+ * $ adb shell am start -n <BrowseActivityComponent> -e spaActivityDestination HOME
+ * $ adb shell am start -n <BrowseActivityComponent> -e spaActivityDestination ARGUMENT/bar/5
+ * ```
  */
 open class BrowseActivity : ComponentActivity() {
     private val spaEnvironment get() = SpaEnvironmentFactory.instance
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 7f2af92..ca75b77 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -19,6 +19,9 @@
 import android.os.Bundle
 import android.widget.Toast
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ProvidedValue
+import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
@@ -28,6 +31,16 @@
 const val INJECT_ENTRY_NAME = "INJECT"
 const val ROOT_ENTRY_NAME = "ROOT"
 
+interface EntryData {
+    val pageId: String
+    val entryId: String
+    val isHighlighted: Boolean
+        get() = false
+}
+
+val LocalEntryDataProvider =
+    compositionLocalOf<EntryData> { error("LocalEntryDataProvider: No Default Value!") }
+
 /**
  * Defines data of a Settings entry.
  */
@@ -121,7 +134,22 @@
             // TODO: Add highlight entry logic
             Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show()
         }
-        uiLayoutImpl(fullArgument(runtimeArguments))
+
+        CompositionLocalProvider(provideLocalEntryData()) {
+            uiLayoutImpl(fullArgument(runtimeArguments))
+        }
+    }
+
+    @Composable
+    fun provideLocalEntryData(): ProvidedValue<EntryData> {
+        val controller = LocalNavController.current
+        return LocalEntryDataProvider provides remember {
+            object : EntryData {
+                override val pageId = containerPage().id
+                override val entryId = id
+                override val isHighlighted = controller.highlightEntryId == id
+            }
+        }
     }
 }
 
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index c5bea2e..79677eea 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -4,6 +4,7 @@
         android:sharedUserId="android.uid.system">
 
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application android:allowClearUserData="false"
                  android:label="@string/app_label"
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8efec67..3a25d85 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -86,6 +86,7 @@
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.ServiceManager;
+import android.os.SystemConfigManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -110,12 +111,11 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.providers.settings.SettingsState.Setting;
-import com.android.server.SystemConfig;
-
-import com.google.android.collect.Sets;
 
 import libcore.util.HexEncoding;
 
+import com.google.android.collect.Sets;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
@@ -368,6 +368,8 @@
     // We have to call in the package manager with no lock held,
     private volatile IPackageManager mPackageManager;
 
+    private volatile SystemConfigManager mSysConfigManager;
+
     @GuardedBy("mLock")
     private boolean mSyncConfigDisabledUntilReboot;
 
@@ -397,6 +399,7 @@
         synchronized (mLock) {
             mUserManager = UserManager.get(getContext());
             mPackageManager = AppGlobals.getPackageManager();
+            mSysConfigManager = getContext().getSystemService(SystemConfigManager.class);
             mHandlerThread = new HandlerThread(LOG_TAG,
                     Process.THREAD_PRIORITY_BACKGROUND);
             mHandlerThread.start();
@@ -3875,8 +3878,7 @@
                     Setting currentSetting = secureSettings.getSettingLocked(
                             Settings.Secure.ENABLED_VR_LISTENERS);
                     if (currentSetting.isNull()) {
-                        ArraySet<ComponentName> l =
-                                SystemConfig.getInstance().getDefaultVrComponents();
+                        List<ComponentName> l = mSysConfigManager.getDefaultVrComponents();
 
                         if (l != null && !l.isEmpty()) {
                             StringBuilder b = new StringBuilder();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index cd667ca..765ee89 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -98,7 +98,7 @@
     private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000;
 
     public static final int MAX_BYTES_PER_APP_PACKAGE_UNLIMITED = -1;
-    public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 20000;
+    public static final int MAX_BYTES_PER_APP_PACKAGE_LIMITED = 40000;
 
     public static final int VERSION_UNDEFINED = -1;
 
@@ -732,19 +732,19 @@
     }
 
     @GuardedBy("mLock")
-    private int getNewMemoryUsagePerPackageLocked(String packageName, int deltaKeySize,
+    private int getNewMemoryUsagePerPackageLocked(String packageName, int deltaKeyLength,
             String oldValue, String newValue, String oldDefaultValue, String newDefaultValue) {
         if (isExemptFromMemoryUsageCap(packageName)) {
             return 0;
         }
-        final Integer currentSize = mPackageToMemoryUsage.get(packageName);
-        final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
-        final int newValueSize = (newValue != null) ? newValue.length() : 0;
-        final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
-        final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
-        final int deltaSize = deltaKeySize + newValueSize + newDefaultValueSize
-                - oldValueSize - oldDefaultValueSize;
-        return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+        final int currentSize = mPackageToMemoryUsage.getOrDefault(packageName, 0);
+        final int oldValueLength = (oldValue != null) ? oldValue.length() : 0;
+        final int newValueLength = (newValue != null) ? newValue.length() : 0;
+        final int oldDefaultValueLength = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
+        final int newDefaultValueLength = (newDefaultValue != null) ? newDefaultValue.length() : 0;
+        final int deltaSize = (deltaKeyLength + newValueLength + newDefaultValueLength
+                - oldValueLength - oldDefaultValueLength) * Character.BYTES;
+        return Math.max(currentSize + deltaSize, 0);
     }
 
     @GuardedBy("mLock")
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 5a534b9..4ed28d5 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -329,46 +329,47 @@
         final String testKey1 = SETTING_NAME;
         final String testValue1 = Strings.repeat("A", 100);
         settingsState.insertSettingLocked(testKey1, testValue1, null, true, TEST_PACKAGE);
-        int expectedMemUsage = testKey1.length() + testValue1.length()
-                + testValue1.length() /* size for default */;
+        int expectedMemUsage = (testKey1.length() + testValue1.length()
+                + testValue1.length() /* size for default */) * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test inserting another key
         final String testKey2 = SETTING_NAME + "2";
         settingsState.insertSettingLocked(testKey2, testValue1, null, false, TEST_PACKAGE);
-        expectedMemUsage += testKey2.length() + testValue1.length();
+        expectedMemUsage += (testKey2.length() + testValue1.length()) * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test updating first key with new default
         final String testValue2 = Strings.repeat("A", 300);
         settingsState.insertSettingLocked(testKey1, testValue2, null, true, TEST_PACKAGE);
-        expectedMemUsage += (testValue2.length() - testValue1.length()) * 2;
+        expectedMemUsage += (testValue2.length() - testValue1.length()) * 2 * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test updating first key without new default
         final String testValue3 = Strings.repeat("A", 50);
         settingsState.insertSettingLocked(testKey1, testValue3, null, false, TEST_PACKAGE);
-        expectedMemUsage -= testValue2.length() - testValue3.length();
+        expectedMemUsage -= (testValue2.length() - testValue3.length()) * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test updating second key
         settingsState.insertSettingLocked(testKey2, testValue2, null, false, TEST_PACKAGE);
-        expectedMemUsage -= testValue1.length() - testValue2.length();
+        expectedMemUsage -= (testValue1.length() - testValue2.length()) * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test resetting key
         settingsState.resetSettingLocked(testKey1);
-        expectedMemUsage += testValue2.length() - testValue3.length();
+        expectedMemUsage += (testValue2.length() - testValue3.length()) * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test resetting default value
         settingsState.resetSettingDefaultValueLocked(testKey1);
-        expectedMemUsage -= testValue2.length();
+        expectedMemUsage -= testValue2.length() * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test deletion
         settingsState.deleteSettingLocked(testKey2);
-        expectedMemUsage -= testValue2.length() + testKey2.length() /* key is deleted too */;
+        expectedMemUsage -= (testValue2.length() + testKey2.length() /* key is deleted too */)
+                * Character.BYTES;
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
 
         // Test another package with a different key
@@ -376,7 +377,8 @@
         final String testKey3 = SETTING_NAME + "3";
         settingsState.insertSettingLocked(testKey3, testValue1, null, true, testPackage2);
         assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
-        final int expectedMemUsage2 = testKey3.length() + testValue1.length() * 2;
+        final int expectedMemUsage2 = (testKey3.length() + testValue1.length() * 2)
+                * Character.BYTES;
         assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2));
 
         // Test system package
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 9476912..cb37c07 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -199,8 +199,8 @@
             }
             mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2));
             return null;
-        }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), any(),
-                anyBoolean());
+        }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
+                any(), anyBoolean());
 
         setWarningState(mContext, STATE_HIDE);
 
@@ -543,7 +543,7 @@
         getInstrumentation().waitForIdleSync();
 
         verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(),
-                anyInt(), any(), anyBoolean());
+                anyInt(), anyInt(), any(), anyBoolean());
         sendBugreportFinished();
     }
 
@@ -608,7 +608,7 @@
         ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass(
                 IDumpstateListener.class);
         verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(),
-                anyInt(), listenerCap.capture(), anyBoolean());
+                anyInt(), anyInt(), listenerCap.capture(), anyBoolean());
         mIDumpstateListener = listenerCap.getValue();
         assertNotNull("Dumpstate listener should not be null", mIDumpstateListener);
         mIDumpstateListener.onProgress(0);
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index d90156d..8135aaa 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -241,4 +241,6 @@
     <string name="clock_title_bubble">Bubble</string>
     <!-- Name of the "Analog" clock face [CHAR LIMIT=15]-->
     <string name="clock_title_analog">Analog</string>
+    <!-- Title of bouncer when we want to authenticate before continuing with action. [CHAR LIMIT=NONE] -->
+    <string name="keyguard_unlock_to_continue">Unlock your device to continue</string>
 </resources>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 1a1fc75..0e9abee 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.screenshot.DraggableConstraintLayout
+<com.android.systemui.clipboardoverlay.ClipboardOverlayView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -157,4 +157,4 @@
             android:layout_margin="@dimen/overlay_dismiss_button_margin"
             android:src="@drawable/overlay_cancel"/>
     </FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
+</com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
new file mode 100644
index 0000000..1a1fc75
--- /dev/null
+++ b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.screenshot.DraggableConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/clipboard_ui"
+    android:theme="@style/FloatingOverlay"
+    android:alpha="0"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:contentDescription="@string/clipboard_overlay_window_name">
+    <ImageView
+        android:id="@+id/actions_container_background"
+        android:visibility="gone"
+        android:layout_height="0dp"
+        android:layout_width="0dp"
+        android:elevation="4dp"
+        android:background="@drawable/action_chip_container_background"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        app:layout_constraintBottom_toBottomOf="@+id/actions_container"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/actions_container"
+        app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
+    <HorizontalScrollView
+        android:id="@+id/actions_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+        android:paddingEnd="@dimen/overlay_action_container_padding_right"
+        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
+        android:elevation="4dp"
+        android:scrollbars="none"
+        android:layout_marginBottom="4dp"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintWidth_percent="1.0"
+        app:layout_constraintWidth_max="wrap"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/preview_border"
+        app:layout_constraintEnd_toEndOf="parent">
+        <LinearLayout
+            android:id="@+id/actions"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:animateLayoutChanges="true">
+            <include layout="@layout/overlay_action_chip"
+                     android:id="@+id/share_chip"/>
+            <include layout="@layout/overlay_action_chip"
+                     android:id="@+id/remote_copy_chip"/>
+            <include layout="@layout/overlay_action_chip"
+                     android:id="@+id/edit_chip"/>
+        </LinearLayout>
+    </HorizontalScrollView>
+    <View
+        android:id="@+id/preview_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/overlay_offset_x"
+        android:layout_marginBottom="12dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:elevation="7dp"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+        android:background="@drawable/overlay_border"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierMargin="@dimen/overlay_border_width"
+        app:barrierDirection="end"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="top"
+        app:barrierMargin="@dimen/overlay_border_width_neg"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <FrameLayout
+        android:id="@+id/clipboard_preview"
+        android:elevation="7dp"
+        android:background="@drawable/overlay_preview_background"
+        android:clipChildren="true"
+        android:clipToOutline="true"
+        android:clipToPadding="true"
+        android:layout_width="@dimen/clipboard_preview_size"
+        android:layout_margin="@dimen/overlay_border_width"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        app:layout_constraintBottom_toBottomOf="@id/preview_border"
+        app:layout_constraintStart_toStartOf="@id/preview_border"
+        app:layout_constraintEnd_toEndOf="@id/preview_border"
+        app:layout_constraintTop_toTopOf="@id/preview_border">
+        <TextView android:id="@+id/text_preview"
+                  android:textFontWeight="500"
+                  android:padding="8dp"
+                  android:gravity="center|start"
+                  android:ellipsize="end"
+                  android:autoSizeTextType="uniform"
+                  android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
+                  android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
+                  android:textColor="?attr/overlayButtonTextColor"
+                  android:textColorLink="?attr/overlayButtonTextColor"
+                  android:background="?androidprv:attr/colorAccentSecondary"
+                  android:layout_width="@dimen/clipboard_preview_size"
+                  android:layout_height="@dimen/clipboard_preview_size"/>
+        <ImageView
+            android:id="@+id/image_preview"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            android:contentDescription="@string/clipboard_image_preview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/hidden_preview"
+            android:visibility="gone"
+            android:textFontWeight="500"
+            android:padding="8dp"
+            android:gravity="center"
+            android:textSize="14sp"
+            android:textColor="?attr/overlayButtonTextColor"
+            android:background="?androidprv:attr/colorAccentSecondary"
+            android:layout_width="@dimen/clipboard_preview_size"
+            android:layout_height="@dimen/clipboard_preview_size"/>
+    </FrameLayout>
+    <FrameLayout
+        android:id="@+id/dismiss_button"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="10dp"
+        android:visibility="gone"
+        android:alpha="0"
+        app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+        app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+        android:contentDescription="@string/clipboard_dismiss_description">
+        <ImageView
+            android:id="@+id/dismiss_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:src="@drawable/overlay_cancel"/>
+    </FrameLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c34db15..93ee151 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -67,7 +67,6 @@
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowManager;
-import android.widget.AdapterView;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -318,7 +317,8 @@
     }
 
     void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
-            UserSwitcherController userSwitcherController) {
+            UserSwitcherController userSwitcherController,
+            UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) {
         if (mCurrentMode == mode) return;
         Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
                 + modeToString(mode));
@@ -330,7 +330,7 @@
                 mViewMode = new OneHandedViewMode();
                 break;
             case MODE_USER_SWITCHER:
-                mViewMode = new UserSwitcherViewMode();
+                mViewMode = new UserSwitcherViewMode(userSwitcherCallback);
                 break;
             default:
                 mViewMode = new DefaultViewMode();
@@ -864,6 +864,12 @@
         private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
                 this::setupUserSwitcher;
 
+        private UserSwitcherCallback mUserSwitcherCallback;
+
+        UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
+            mUserSwitcherCallback = userSwitcherCallback;
+        }
+
         @Override
         public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
@@ -1040,34 +1046,25 @@
                 }
             };
 
-            if (adapter.getCount() < 2) {
-                // The drop down arrow is at index 1
-                ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(0);
-                anchor.setClickable(false);
-                return;
-            } else {
-                ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255);
-            }
-
             anchor.setOnClickListener((v) -> {
                 if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
                 mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
                 mPopup.setAnchorView(anchor);
                 mPopup.setAdapter(adapter);
-                mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-                        public void onItemClick(AdapterView parent, View view, int pos, long id) {
-                            if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
-                            if (!view.isEnabled()) return;
-
-                            // Subtract one for the header
-                            UserRecord user = adapter.getItem(pos - 1);
-                            if (!user.isCurrent) {
-                                adapter.onUserListItemClicked(user);
-                            }
-                            mPopup.dismiss();
-                            mPopup = null;
-                        }
-                    });
+                mPopup.setOnItemClickListener((parent, view, pos, id) -> {
+                    if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
+                    if (!view.isEnabled()) return;
+                    // Subtract one for the header
+                    UserRecord user = adapter.getItem(pos - 1);
+                    if (user.isManageUsers || user.isAddSupervisedUser) {
+                        mUserSwitcherCallback.showUnlockToContinueMessage();
+                    }
+                    if (!user.isCurrent) {
+                        adapter.onUserListItemClicked(user);
+                    }
+                    mPopup.dismiss();
+                    mPopup = null;
+                });
                 mPopup.show();
             });
         }
@@ -1122,6 +1119,10 @@
                 constraintSet.applyTo(mView);
             }
         }
+
+        interface UserSwitcherCallback {
+            void showUnlockToContinueMessage();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d448f40..bcd1a1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -620,7 +620,9 @@
             mode = KeyguardSecurityContainer.MODE_ONE_HANDED;
         }
 
-        mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController);
+        mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController,
+                () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue),
+                        null));
     }
 
     public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 9eb2c11..c9128e5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -109,12 +109,13 @@
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     runningSecurityShiftAnimator = null
+                    if (shouldRestoreLayerType) {
+                        v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
+                    }
                 }
             }
         )
 
-        var finishedFadingOutNonSecurityView = false
-
         runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
             val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
             val isFadingOut = animation.animatedFraction < switchPoint
@@ -153,6 +154,13 @@
                         startRect.right + currentTranslation,
                         startRect.bottom
                     )
+                } else {
+                    v.setLeftTopRightBottom(
+                        startRect.left,
+                        startRect.top,
+                        startRect.right,
+                        startRect.bottom
+                    )
                 }
             } else {
                 // And in again over the remaining (100-X)%.
@@ -175,32 +183,13 @@
                         endRect.right - translationRemaining,
                         endRect.bottom
                     )
-                }
-            }
-            if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) {
-                v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
-            }
-
-            // For views that are not the security view flipper, we do not want to apply
-            // an x translation animation. Instead, we want to fade out, move to final position and
-            // then fade in.
-            if (v !is KeyguardSecurityViewFlipper) {
-                // Opacity goes close to 0 but does not fully get to 0.
-                if (opacity - 0.001f < 0f) {
+                } else {
                     v.setLeftTopRightBottom(
                         endRect.left,
                         endRect.top,
                         endRect.right,
                         endRect.bottom
                     )
-                    finishedFadingOutNonSecurityView = true
-                } else if (!finishedFadingOutNonSecurityView) {
-                    v.setLeftTopRightBottom(
-                        startRect.left,
-                        startRect.top,
-                        startRect.right,
-                        startRect.bottom
-                    )
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 05e3f1c..82e5704 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -31,9 +31,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * ClipboardListener brings up a clipboard overlay when something is copied to the clipboard.
@@ -51,20 +54,30 @@
 
     private final Context mContext;
     private final DeviceConfigProxy mDeviceConfig;
-    private final ClipboardOverlayControllerFactory mOverlayFactory;
+    private final Provider<ClipboardOverlayController> mOverlayProvider;
+    private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
     private final ClipboardManager mClipboardManager;
     private final UiEventLogger mUiEventLogger;
-    private ClipboardOverlayController mClipboardOverlayController;
+    private final FeatureFlags mFeatureFlags;
+    private boolean mUsingNewOverlay;
+    private ClipboardOverlay mClipboardOverlay;
 
     @Inject
     public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
-            ClipboardOverlayControllerFactory overlayFactory, ClipboardManager clipboardManager,
-            UiEventLogger uiEventLogger) {
+            Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
+            ClipboardOverlayControllerLegacyFactory overlayFactory,
+            ClipboardManager clipboardManager,
+            UiEventLogger uiEventLogger,
+            FeatureFlags featureFlags) {
         mContext = context;
         mDeviceConfig = deviceConfigProxy;
+        mOverlayProvider = clipboardOverlayControllerProvider;
         mOverlayFactory = overlayFactory;
         mClipboardManager = clipboardManager;
         mUiEventLogger = uiEventLogger;
+        mFeatureFlags = featureFlags;
+
+        mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
     }
 
     @Override
@@ -89,16 +102,22 @@
             return;
         }
 
-        if (mClipboardOverlayController == null) {
-            mClipboardOverlayController = mOverlayFactory.create(mContext);
+        boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
+        if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
+            mUsingNewOverlay = enabled;
+            if (enabled) {
+                mClipboardOverlay = mOverlayProvider.get();
+            } else {
+                mClipboardOverlay = mOverlayFactory.create(mContext);
+            }
             mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
         } else {
             mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
         }
-        mClipboardOverlayController.setClipData(clipData, clipSource);
-        mClipboardOverlayController.setOnSessionCompleteListener(() -> {
+        mClipboardOverlay.setClipData(clipData, clipSource);
+        mClipboardOverlay.setOnSessionCompleteListener(() -> {
             // Session is complete, free memory until it's needed again.
-            mClipboardOverlayController = null;
+            mClipboardOverlay = null;
         });
     }
 
@@ -120,4 +139,10 @@
     private static boolean isEmulator() {
         return SystemProperties.getBoolean("ro.boot.qemu", false);
     }
+
+    interface ClipboardOverlay {
+        void setClipData(ClipData clipData, String clipSource);
+
+        void setOnSessionCompleteListener(Runnable runnable);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 7e499eb..bfb27a4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.clipboardoverlay;
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
@@ -37,11 +36,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.MainThread;
-import android.app.ICompatCameraControlCallback;
 import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
@@ -52,14 +46,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputManager;
 import android.net.Uri;
@@ -67,57 +54,37 @@
 import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.MathUtils;
 import android.util.Size;
-import android.util.TypedValue;
 import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.Gravity;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLinks;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.screenshot.DraggableConstraintLayout;
-import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
 import com.android.systemui.screenshot.TimeoutHandler;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Optional;
+
+import javax.inject.Inject;
 
 /**
  * Controls state and UI for the overlay that appears when something is added to the clipboard
  */
-public class ClipboardOverlayController {
+public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
     private static final String TAG = "ClipboardOverlayCtrlr";
 
     /** Constants for screenshot/copy deconflicting */
@@ -126,36 +93,22 @@
     public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
 
     private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
-    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
-    private static final int FONT_SEARCH_STEP_PX = 4;
 
     private final Context mContext;
     private final ClipboardLogger mClipboardLogger;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final DisplayManager mDisplayManager;
-    private final DisplayMetrics mDisplayMetrics;
-    private final WindowManager mWindowManager;
-    private final WindowManager.LayoutParams mWindowLayoutParams;
-    private final PhoneWindow mWindow;
+    private final ClipboardOverlayWindow mWindow;
     private final TimeoutHandler mTimeoutHandler;
-    private final AccessibilityManager mAccessibilityManager;
     private final TextClassifier mTextClassifier;
 
-    private final DraggableConstraintLayout mView;
-    private final View mClipboardPreview;
-    private final ImageView mImagePreview;
-    private final TextView mTextPreview;
-    private final TextView mHiddenPreview;
-    private final View mPreviewBorder;
-    private final OverlayActionChip mEditChip;
-    private final OverlayActionChip mShareChip;
-    private final OverlayActionChip mRemoteCopyChip;
-    private final View mActionContainerBackground;
-    private final View mDismissButton;
-    private final LinearLayout mActionContainer;
-    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+    private final ClipboardOverlayView mView;
 
     private Runnable mOnSessionCompleteListener;
+    private Runnable mOnRemoteCopyTapped;
+    private Runnable mOnShareTapped;
+    private Runnable mOnEditTapped;
+    private Runnable mOnPreviewTapped;
 
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
@@ -163,14 +116,66 @@
     private BroadcastReceiver mCloseDialogsReceiver;
     private BroadcastReceiver mScreenshotReceiver;
 
-    private boolean mBlockAttach = false;
     private Animator mExitAnimator;
     private Animator mEnterAnimator;
-    private final int mOrientation;
-    private boolean mKeyboardVisible;
 
+    private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
+            new ClipboardOverlayView.ClipboardOverlayCallbacks() {
+                @Override
+                public void onInteraction() {
+                    mTimeoutHandler.resetTimeout();
+                }
 
-    public ClipboardOverlayController(Context context,
+                @Override
+                public void onSwipeDismissInitiated(Animator animator) {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+                    mExitAnimator = animator;
+                }
+
+                @Override
+                public void onDismissComplete() {
+                    hideImmediate();
+                }
+
+                @Override
+                public void onPreviewTapped() {
+                    if (mOnPreviewTapped != null) {
+                        mOnPreviewTapped.run();
+                    }
+                }
+
+                @Override
+                public void onShareButtonTapped() {
+                    if (mOnShareTapped != null) {
+                        mOnShareTapped.run();
+                    }
+                }
+
+                @Override
+                public void onEditButtonTapped() {
+                    if (mOnEditTapped != null) {
+                        mOnEditTapped.run();
+                    }
+                }
+
+                @Override
+                public void onRemoteCopyButtonTapped() {
+                    if (mOnRemoteCopyTapped != null) {
+                        mOnRemoteCopyTapped.run();
+                    }
+                }
+
+                @Override
+                public void onDismissButtonTapped() {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+                    animateOut();
+                }
+            };
+
+    @Inject
+    public ClipboardOverlayController(@OverlayWindowContext Context context,
+            ClipboardOverlayView clipboardOverlayView,
+            ClipboardOverlayWindow clipboardOverlayWindow,
             BroadcastDispatcher broadcastDispatcher,
             BroadcastSender broadcastSender,
             TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
@@ -181,121 +186,26 @@
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+        mView = clipboardOverlayView;
+        mWindow = clipboardOverlayWindow;
+        mWindow.init(mView::setInsets, () -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+            hideImmediate();
+        });
+
         mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
                 .getTextClassifier();
 
-        mWindowManager = mContext.getSystemService(WindowManager.class);
-
-        mDisplayMetrics = new DisplayMetrics();
-        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
         mTimeoutHandler = timeoutHandler;
         mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
 
-        // Setup the window that we are going to use
-        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
-        mWindowLayoutParams.setTitle("ClipboardOverlay");
+        mView.setCallbacks(mClipboardCallbacks);
 
-        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
-        mWindow.setWindowManager(mWindowManager, null, null);
 
-        setWindowFocusable(false);
-
-        mView = (DraggableConstraintLayout)
-                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay, null);
-        mActionContainerBackground =
-                requireNonNull(mView.findViewById(R.id.actions_container_background));
-        mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
-        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
-        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
-        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
-        mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
-        mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
-        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
-        mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
-        mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
-        mEditChip.setAlpha(1);
-        mShareChip.setAlpha(1);
-        mRemoteCopyChip.setAlpha(1);
-        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
-
-        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
-        mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
-            @Override
-            public void onInteraction() {
-                mTimeoutHandler.resetTimeout();
-            }
-
-            @Override
-            public void onSwipeDismissInitiated(Animator animator) {
-                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
-                mExitAnimator = animator;
-            }
-
-            @Override
-            public void onDismissComplete() {
-                hideImmediate();
-            }
-        });
-
-        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
-            int availableHeight = mTextPreview.getHeight()
-                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
-            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
-            return true;
-        });
-
-        mDismissButton.setOnClickListener(view -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
-            animateOut();
-        });
-
-        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
-        mRemoteCopyChip.setIcon(
-                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
-        mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
-        mOrientation = mContext.getResources().getConfiguration().orientation;
-
-        attachWindow();
-        withWindowAttached(() -> {
+        mWindow.withWindowAttached(() -> {
             mWindow.setContentView(mView);
-            WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-            mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
-            updateInsets(insets);
-            mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
-                    new ViewTreeObserver.OnGlobalLayoutListener() {
-                        @Override
-                        public void onGlobalLayout() {
-                            WindowInsets insets =
-                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-                            boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
-                            if (keyboardVisible != mKeyboardVisible) {
-                                mKeyboardVisible = keyboardVisible;
-                                updateInsets(insets);
-                            }
-                        }
-                    });
-            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
-                    new ViewRootImpl.ActivityConfigCallback() {
-                        @Override
-                        public void onConfigurationChanged(Configuration overrideConfig,
-                                int newDisplayId) {
-                            if (mContext.getResources().getConfiguration().orientation
-                                    != mOrientation) {
-                                mClipboardLogger.logSessionComplete(
-                                        CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                                hideImmediate();
-                            }
-                        }
-
-                        @Override
-                        public void requestCompatCameraControl(
-                                boolean showControl, boolean transformationApplied,
-                                ICompatCameraControlCallback callback) {
-                            Log.w(TAG, "unexpected requestCompatCameraControl call");
-                        }
-                    });
+            mView.setInsets(mWindow.getWindowInsets(),
+                    mContext.getResources().getConfiguration().orientation);
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
@@ -336,21 +246,19 @@
         broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
     }
 
-    void setClipData(ClipData clipData, String clipSource) {
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setClipData(ClipData clipData, String clipSource) {
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             mExitAnimator.cancel();
         }
         reset();
-        String accessibilityAnnouncement;
+        String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
 
         boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
                 && clipData.getDescription().getExtras()
                 .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
         if (clipData == null || clipData.getItemCount() == 0) {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+            mView.showDefaultTextPreview();
         } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
             ClipData.Item item = clipData.getItemAt(0);
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -360,53 +268,47 @@
                 }
             }
             if (isSensitive) {
-                showEditableText(
-                        mContext.getResources().getString(R.string.clipboard_asterisks), true);
+                showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
             } else {
                 showEditableText(item.getText(), false);
             }
-            showShareChip(clipData);
+            mOnShareTapped = () -> shareContent(clipData);
+            mView.showShareChip();
             accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
         } else if (clipData.getItemAt(0).getUri() != null) {
             if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
-                showShareChip(clipData);
+                mOnShareTapped = () -> shareContent(clipData);
+                mView.showShareChip();
                 accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
-            } else {
-                accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
             }
         } else {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+            mView.showDefaultTextPreview();
         }
+        maybeShowRemoteCopy(clipData);
+        animateIn();
+        mView.announceForAccessibility(accessibilityAnnouncement);
+        mTimeoutHandler.resetTimeout();
+    }
+
+    private void maybeShowRemoteCopy(ClipData clipData) {
         Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
         // Only show remote copy if it's available.
         PackageManager packageManager = mContext.getPackageManager();
         if (packageManager.resolveActivity(
                 remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
-            mRemoteCopyChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_send_nearby_description));
-            mRemoteCopyChip.setVisibility(View.VISIBLE);
-            mRemoteCopyChip.setOnClickListener((v) -> {
+            mView.setRemoteCopyVisibility(true);
+            mOnRemoteCopyTapped = () -> {
                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
                 mContext.startActivity(remoteCopyIntent);
                 animateOut();
-            });
-            mActionContainerBackground.setVisibility(View.VISIBLE);
+            };
         } else {
-            mRemoteCopyChip.setVisibility(View.GONE);
+            mView.setRemoteCopyVisibility(false);
         }
-        withWindowAttached(() -> {
-            if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
-                mView.post(this::animateIn);
-            }
-            mView.announceForAccessibility(accessibilityAnnouncement);
-        });
-        mTimeoutHandler.resetTimeout();
     }
 
-    void setOnSessionCompleteListener(Runnable runnable) {
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setOnSessionCompleteListener(Runnable runnable) {
         mOnSessionCompleteListener = runnable;
     }
 
@@ -418,72 +320,29 @@
             actions.addAll(classification.getActions());
         }
         mView.post(() -> {
-            resetActionChips();
-            if (actions.size() > 0) {
-                mActionContainerBackground.setVisibility(View.VISIBLE);
-                for (RemoteAction action : actions) {
-                    Intent targetIntent = action.getActionIntent().getIntent();
-                    ComponentName component = targetIntent.getComponent();
-                    if (component != null && !TextUtils.equals(source,
-                            component.getPackageName())) {
-                        OverlayActionChip chip = constructActionChip(action);
-                        mActionContainer.addView(chip);
-                        mActionChips.add(chip);
-                        break; // only show at most one action chip
-                    }
-                }
-            }
+            Optional<RemoteAction> action = actions.stream().filter(remoteAction -> {
+                ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+                return component != null && !TextUtils.equals(source, component.getPackageName());
+            }).findFirst();
+            mView.resetActionChips();
+            action.ifPresent(remoteAction -> mView.setActionChip(remoteAction, () -> {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                animateOut();
+            }));
         });
     }
 
-    private void showShareChip(ClipData clip) {
-        mShareChip.setVisibility(View.VISIBLE);
-        mActionContainerBackground.setVisibility(View.VISIBLE);
-        mShareChip.setOnClickListener((v) -> shareContent(clip));
-    }
-
-    private OverlayActionChip constructActionChip(RemoteAction action) {
-        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
-                R.layout.overlay_action_chip, mActionContainer, false);
-        chip.setText(action.getTitle());
-        chip.setContentDescription(action.getTitle());
-        chip.setIcon(action.getIcon(), false);
-        chip.setPendingIntent(action.getActionIntent(), () -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
-            animateOut();
-        });
-        chip.setAlpha(1);
-        return chip;
-    }
-
     private void monitorOutsideTouches() {
         InputManager inputManager = mContext.getSystemService(InputManager.class);
         mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
-        mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
-                Looper.getMainLooper()) {
+        mInputEventReceiver = new InputEventReceiver(
+                mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
             @Override
             public void onInputEvent(InputEvent event) {
                 if (event instanceof MotionEvent) {
                     MotionEvent motionEvent = (MotionEvent) event;
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                        Region touchRegion = new Region();
-
-                        final Rect tmpRect = new Rect();
-                        mPreviewBorder.getBoundsOnScreen(tmpRect);
-                        tmpRect.inset(
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-                                        -SWIPE_PADDING_DP));
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        mActionContainerBackground.getBoundsOnScreen(tmpRect);
-                        tmpRect.inset(
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-                                        -SWIPE_PADDING_DP));
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        mDismissButton.getBoundsOnScreen(tmpRect);
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        if (!touchRegion.contains(
+                        if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
                             mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                             animateOut();
@@ -513,95 +372,27 @@
         animateOut();
     }
 
-    private void showSinglePreview(View v) {
-        mTextPreview.setVisibility(View.GONE);
-        mImagePreview.setVisibility(View.GONE);
-        mHiddenPreview.setVisibility(View.GONE);
-        v.setVisibility(View.VISIBLE);
-    }
-
-    private void showTextPreview(CharSequence text, TextView textView) {
-        showSinglePreview(textView);
-        final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
-        textView.setText(truncatedText);
-        updateTextSize(truncatedText, textView);
-
-        textView.addOnLayoutChangeListener(
-                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                    if (right - left != oldRight - oldLeft) {
-                        updateTextSize(truncatedText, textView);
-                    }
-                });
-        mEditChip.setVisibility(View.GONE);
-    }
-
-    private void updateTextSize(CharSequence text, TextView textView) {
-        Paint paint = new Paint(textView.getPaint());
-        Resources res = textView.getResources();
-        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
-        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
-        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
-            // If the text is a single word and would fit within the TextView at the min font size,
-            // find the biggest font size that will fit.
-            float fontSizePx = minFontSize;
-            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
-                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
-                fontSizePx += FONT_SEARCH_STEP_PX;
-            }
-            // Need to turn off autosizing, otherwise setTextSize is a no-op.
-            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
-            // It's possible to hit the max font size and not fill the width, so centering
-            // horizontally looks better in this case.
-            textView.setGravity(Gravity.CENTER);
-            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
-        } else {
-            // Otherwise just stick with autosize.
-            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
-                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
-            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
-        }
-    }
-
-    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
-            float fontSizePx) {
-        paint.setTextSize(fontSizePx);
-        float size = paint.measureText(text.toString());
-        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
-                - textView.getPaddingRight();
-        return size < availableWidth;
-    }
-
-    private static boolean isOneWord(CharSequence text) {
-        return text.toString().split("\\s+", 2).length == 1;
-    }
-
     private void showEditableText(CharSequence text, boolean hidden) {
-        TextView textView = hidden ? mHiddenPreview : mTextPreview;
-        showTextPreview(text, textView);
-        View.OnClickListener listener = v -> editText();
-        setAccessibilityActionToEdit(textView);
+        mView.showTextPreview(text, hidden);
+        mView.setEditAccessibilityAction(true);
+        mOnPreviewTapped = this::editText;
         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                 CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
-            mEditChip.setVisibility(View.VISIBLE);
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-            mEditChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_edit_text_description));
-            mEditChip.setOnClickListener(listener);
+            mOnEditTapped = this::editText;
+            mView.showEditChip(mContext.getString(R.string.clipboard_edit_text_description));
         }
-        textView.setOnClickListener(listener);
     }
 
     private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
-        View.OnClickListener listener = v -> editImage(uri);
+        Runnable listener = () -> editImage(uri);
         ContentResolver resolver = mContext.getContentResolver();
         String mimeType = resolver.getType(uri);
         boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
         if (isSensitive) {
-            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
-            showSinglePreview(mHiddenPreview);
+            mView.showImagePreview(null);
             if (isEditableImage) {
-                mHiddenPreview.setOnClickListener(listener);
-                setAccessibilityActionToEdit(mHiddenPreview);
+                mOnPreviewTapped = listener;
+                mView.setEditAccessibilityAction(true);
             }
         } else if (isEditableImage) { // if the MIMEtype is image, try to load
             try {
@@ -609,44 +400,36 @@
                 // The width of the view is capped, height maintains aspect ratio, so allow it to be
                 // taller if needed.
                 Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
-                showSinglePreview(mImagePreview);
-                mImagePreview.setImageBitmap(thumbnail);
-                mImagePreview.setOnClickListener(listener);
-                setAccessibilityActionToEdit(mImagePreview);
+                mView.showImagePreview(thumbnail);
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = listener;
             } catch (IOException e) {
                 Log.e(TAG, "Thumbnail loading failed", e);
-                showTextPreview(
-                        mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                        mTextPreview);
+                mView.showDefaultTextPreview();
                 isEditableImage = false;
             }
         } else {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
+            mView.showDefaultTextPreview();
         }
         if (isEditableImage && DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
-            mEditChip.setVisibility(View.VISIBLE);
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-            mEditChip.setOnClickListener(listener);
-            mEditChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_edit_image_description));
+            mView.showEditChip(mContext.getString(R.string.clipboard_edit_image_description));
         }
         return isEditableImage;
     }
 
-    private void setAccessibilityActionToEdit(View view) {
-        ViewCompat.replaceAccessibilityAction(view,
-                AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
-                mContext.getString(R.string.clipboard_edit), null);
-    }
-
     private void animateIn() {
-        if (mAccessibilityManager.isEnabled()) {
-            mDismissButton.setVisibility(View.VISIBLE);
+        if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+            return;
         }
-        mEnterAnimator = getEnterAnimation();
+        mEnterAnimator = mView.getEnterAnimation();
+        mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mTimeoutHandler.resetTimeout();
+            }
+        });
         mEnterAnimator.start();
     }
 
@@ -654,7 +437,7 @@
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             return;
         }
-        Animator anim = getExitAnimation();
+        Animator anim = mView.getExitAnimation();
         anim.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
@@ -676,122 +459,11 @@
         anim.start();
     }
 
-    private Animator getEnterAnimation() {
-        TimeInterpolator linearInterpolator = new LinearInterpolator();
-        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
-        AnimatorSet enterAnim = new AnimatorSet();
-
-        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
-        rootAnim.setInterpolator(linearInterpolator);
-        rootAnim.setDuration(66);
-        rootAnim.addUpdateListener(animation -> {
-            mView.setAlpha(animation.getAnimatedFraction());
-        });
-
-        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
-        scaleAnim.setInterpolator(scaleInterpolator);
-        scaleAnim.setDuration(333);
-        scaleAnim.addUpdateListener(animation -> {
-            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
-            mClipboardPreview.setScaleX(previewScale);
-            mClipboardPreview.setScaleY(previewScale);
-            mPreviewBorder.setScaleX(previewScale);
-            mPreviewBorder.setScaleY(previewScale);
-
-            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
-            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
-            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
-            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
-            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
-            mActionContainer.setScaleX(actionsScaleX);
-            mActionContainer.setScaleY(actionsScaleY);
-            mActionContainerBackground.setScaleX(actionsScaleX);
-            mActionContainerBackground.setScaleY(actionsScaleY);
-        });
-
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setInterpolator(linearInterpolator);
-        alphaAnim.setDuration(283);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = animation.getAnimatedFraction();
-            mClipboardPreview.setAlpha(alpha);
-            mPreviewBorder.setAlpha(alpha);
-            mDismissButton.setAlpha(alpha);
-            mActionContainer.setAlpha(alpha);
-        });
-
-        mActionContainer.setAlpha(0);
-        mPreviewBorder.setAlpha(0);
-        mClipboardPreview.setAlpha(0);
-        enterAnim.play(rootAnim).with(scaleAnim);
-        enterAnim.play(alphaAnim).after(50).after(rootAnim);
-
-        enterAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                mView.setAlpha(1);
-                mTimeoutHandler.resetTimeout();
-            }
-        });
-        return enterAnim;
-    }
-
-    private Animator getExitAnimation() {
-        TimeInterpolator linearInterpolator = new LinearInterpolator();
-        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
-        AnimatorSet exitAnim = new AnimatorSet();
-
-        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
-        rootAnim.setInterpolator(linearInterpolator);
-        rootAnim.setDuration(100);
-        rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
-
-        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
-        scaleAnim.setInterpolator(scaleInterpolator);
-        scaleAnim.setDuration(250);
-        scaleAnim.addUpdateListener(animation -> {
-            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
-            mClipboardPreview.setScaleX(previewScale);
-            mClipboardPreview.setScaleY(previewScale);
-            mPreviewBorder.setScaleX(previewScale);
-            mPreviewBorder.setScaleY(previewScale);
-
-            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
-            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
-            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
-            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
-            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
-            mActionContainer.setScaleX(actionScaleX);
-            mActionContainer.setScaleY(actionScaleY);
-            mActionContainerBackground.setScaleX(actionScaleX);
-            mActionContainerBackground.setScaleY(actionScaleY);
-        });
-
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setInterpolator(linearInterpolator);
-        alphaAnim.setDuration(166);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = 1 - animation.getAnimatedFraction();
-            mClipboardPreview.setAlpha(alpha);
-            mPreviewBorder.setAlpha(alpha);
-            mDismissButton.setAlpha(alpha);
-            mActionContainer.setAlpha(alpha);
-        });
-
-        exitAnim.play(alphaAnim).with(scaleAnim);
-        exitAnim.play(rootAnim).after(150).after(alphaAnim);
-        return exitAnim;
-    }
-
     private void hideImmediate() {
         // Note this may be called multiple times if multiple dismissal events happen at the same
         // time.
         mTimeoutHandler.cancelTimeout();
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.removeViewImmediate(decorView);
-        }
+        mWindow.remove();
         if (mCloseDialogsReceiver != null) {
             mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
             mCloseDialogsReceiver = null;
@@ -813,129 +485,20 @@
         }
     }
 
-    private void resetActionChips() {
-        for (OverlayActionChip chip : mActionChips) {
-            mActionContainer.removeView(chip);
-        }
-        mActionChips.clear();
-    }
-
     private void reset() {
-        mView.setTranslationX(0);
-        mView.setAlpha(0);
-        mActionContainerBackground.setVisibility(View.GONE);
-        mShareChip.setVisibility(View.GONE);
-        mEditChip.setVisibility(View.GONE);
-        mRemoteCopyChip.setVisibility(View.GONE);
-        resetActionChips();
+        mOnRemoteCopyTapped = null;
+        mOnShareTapped = null;
+        mOnEditTapped = null;
+        mOnPreviewTapped = null;
+        mView.reset();
         mTimeoutHandler.cancelTimeout();
         mClipboardLogger.reset();
     }
 
-    @MainThread
-    private void attachWindow() {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow() || mBlockAttach) {
-            return;
-        }
-        mBlockAttach = true;
-        mWindowManager.addView(decorView, mWindowLayoutParams);
-        decorView.requestApplyInsets();
-        mView.requestApplyInsets();
-        decorView.getViewTreeObserver().addOnWindowAttachListener(
-                new ViewTreeObserver.OnWindowAttachListener() {
-                    @Override
-                    public void onWindowAttached() {
-                        mBlockAttach = false;
-                    }
-
-                    @Override
-                    public void onWindowDetached() {
-                    }
-                }
-        );
-    }
-
-    private void withWindowAttached(Runnable action) {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow()) {
-            action.run();
-        } else {
-            decorView.getViewTreeObserver().addOnWindowAttachListener(
-                    new ViewTreeObserver.OnWindowAttachListener() {
-                        @Override
-                        public void onWindowAttached() {
-                            mBlockAttach = false;
-                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
-                            action.run();
-                        }
-
-                        @Override
-                        public void onWindowDetached() {
-                        }
-                    });
-        }
-    }
-
-    private void updateInsets(WindowInsets insets) {
-        int orientation = mContext.getResources().getConfiguration().orientation;
-        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
-        if (p == null) {
-            return;
-        }
-        DisplayCutout cutout = insets.getDisplayCutout();
-        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
-        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
-        if (cutout == null) {
-            p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
-        } else {
-            Insets waterfall = cutout.getWaterfallInsets();
-            if (orientation == ORIENTATION_PORTRAIT) {
-                p.setMargins(
-                        waterfall.left,
-                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
-                        waterfall.right,
-                        Math.max(imeInsets.bottom,
-                                Math.max(cutout.getSafeInsetBottom(),
-                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
-            } else {
-                p.setMargins(
-                        waterfall.left,
-                        waterfall.top,
-                        waterfall.right,
-                        Math.max(imeInsets.bottom,
-                                Math.max(navBarInsets.bottom, waterfall.bottom)));
-            }
-        }
-        mView.setLayoutParams(p);
-        mView.requestLayout();
-    }
-
     private Display getDefaultDisplay() {
         return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
     }
 
-    /**
-     * Updates the window focusability.  If the window is already showing, then it updates the
-     * window immediately, otherwise the layout params will be applied when the window is next
-     * shown.
-     */
-    private void setWindowFocusable(boolean focusable) {
-        int flags = mWindowLayoutParams.flags;
-        if (focusable) {
-            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        } else {
-            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        }
-        if (mWindowLayoutParams.flags == flags) {
-            return;
-        }
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
-        }
-    }
-
     static class ClipboardLogger {
         private final UiEventLogger mUiEventLogger;
         private boolean mGuarded = false;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
new file mode 100644
index 0000000..3a040829
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
@@ -0,0 +1,963 @@
+/*
+ * 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.clipboardoverlay;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.app.ICompatCameraControlCallback;
+import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Controls state and UI for the overlay that appears when something is added to the clipboard
+ */
+public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
+    private static final String TAG = "ClipboardOverlayCtrlr";
+    private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
+
+    /** Constants for screenshot/copy deconflicting */
+    public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
+    public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
+    public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
+
+    private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+
+    private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
+    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+    private static final int FONT_SEARCH_STEP_PX = 4;
+
+    private final Context mContext;
+    private final ClipboardLogger mClipboardLogger;
+    private final BroadcastDispatcher mBroadcastDispatcher;
+    private final DisplayManager mDisplayManager;
+    private final DisplayMetrics mDisplayMetrics;
+    private final WindowManager mWindowManager;
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+    private final PhoneWindow mWindow;
+    private final TimeoutHandler mTimeoutHandler;
+    private final AccessibilityManager mAccessibilityManager;
+    private final TextClassifier mTextClassifier;
+
+    private final DraggableConstraintLayout mView;
+    private final View mClipboardPreview;
+    private final ImageView mImagePreview;
+    private final TextView mTextPreview;
+    private final TextView mHiddenPreview;
+    private final View mPreviewBorder;
+    private final OverlayActionChip mEditChip;
+    private final OverlayActionChip mShareChip;
+    private final OverlayActionChip mRemoteCopyChip;
+    private final View mActionContainerBackground;
+    private final View mDismissButton;
+    private final LinearLayout mActionContainer;
+    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+    private Runnable mOnSessionCompleteListener;
+
+    private InputMonitor mInputMonitor;
+    private InputEventReceiver mInputEventReceiver;
+
+    private BroadcastReceiver mCloseDialogsReceiver;
+    private BroadcastReceiver mScreenshotReceiver;
+
+    private boolean mBlockAttach = false;
+    private Animator mExitAnimator;
+    private Animator mEnterAnimator;
+    private final int mOrientation;
+    private boolean mKeyboardVisible;
+
+
+    public ClipboardOverlayControllerLegacy(Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            BroadcastSender broadcastSender,
+            TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+        mBroadcastDispatcher = broadcastDispatcher;
+        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+        final Context displayContext = context.createDisplayContext(getDefaultDisplay());
+        mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+
+        mClipboardLogger = new ClipboardLogger(uiEventLogger);
+
+        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+        mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
+                .getTextClassifier();
+
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+
+        mDisplayMetrics = new DisplayMetrics();
+        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+
+        mTimeoutHandler = timeoutHandler;
+        mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
+
+        // Setup the window that we are going to use
+        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+        mWindowLayoutParams.setTitle("ClipboardOverlay");
+
+        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+        mWindow.setWindowManager(mWindowManager, null, null);
+
+        setWindowFocusable(false);
+
+        mView = (DraggableConstraintLayout)
+                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
+        mActionContainerBackground =
+                requireNonNull(mView.findViewById(R.id.actions_container_background));
+        mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
+        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
+        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
+        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+        mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
+        mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
+        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
+        mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
+        mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+        mEditChip.setAlpha(1);
+        mShareChip.setAlpha(1);
+        mRemoteCopyChip.setAlpha(1);
+        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
+
+        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+        mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
+            @Override
+            public void onInteraction() {
+                mTimeoutHandler.resetTimeout();
+            }
+
+            @Override
+            public void onSwipeDismissInitiated(Animator animator) {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+                mExitAnimator = animator;
+            }
+
+            @Override
+            public void onDismissComplete() {
+                hideImmediate();
+            }
+        });
+
+        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+            int availableHeight = mTextPreview.getHeight()
+                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+            return true;
+        });
+
+        mDismissButton.setOnClickListener(view -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+            animateOut();
+        });
+
+        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+        mRemoteCopyChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+        mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+        mOrientation = mContext.getResources().getConfiguration().orientation;
+
+        attachWindow();
+        withWindowAttached(() -> {
+            mWindow.setContentView(mView);
+            WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+            mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+            updateInsets(insets);
+            mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
+                    new ViewTreeObserver.OnGlobalLayoutListener() {
+                        @Override
+                        public void onGlobalLayout() {
+                            WindowInsets insets =
+                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+                            boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+                            if (keyboardVisible != mKeyboardVisible) {
+                                mKeyboardVisible = keyboardVisible;
+                                updateInsets(insets);
+                            }
+                        }
+                    });
+            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+                    new ViewRootImpl.ActivityConfigCallback() {
+                        @Override
+                        public void onConfigurationChanged(Configuration overrideConfig,
+                                int newDisplayId) {
+                            if (mContext.getResources().getConfiguration().orientation
+                                    != mOrientation) {
+                                mClipboardLogger.logSessionComplete(
+                                        CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                                hideImmediate();
+                            }
+                        }
+
+                        @Override
+                        public void requestCompatCameraControl(
+                                boolean showControl, boolean transformationApplied,
+                                ICompatCameraControlCallback callback) {
+                            Log.w(TAG, "unexpected requestCompatCameraControl call");
+                        }
+                    });
+        });
+
+        mTimeoutHandler.setOnTimeoutRunnable(() -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
+            animateOut();
+        });
+
+        mCloseDialogsReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    animateOut();
+                }
+            }
+        };
+
+        mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
+                new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
+        mScreenshotReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (SCREENSHOT_ACTION.equals(intent.getAction())) {
+                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    animateOut();
+                }
+            }
+        };
+
+        mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
+                new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
+                SELF_PERMISSION);
+        monitorOutsideTouches();
+
+        Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
+        // Set package name so the system knows it's safe
+        copyIntent.setPackage(mContext.getPackageName());
+        broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
+    }
+
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setClipData(ClipData clipData, String clipSource) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            mExitAnimator.cancel();
+        }
+        reset();
+        String accessibilityAnnouncement;
+
+        boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+                && clipData.getDescription().getExtras()
+                .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
+        if (clipData == null || clipData.getItemCount() == 0) {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
+            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+        } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
+            ClipData.Item item = clipData.getItemAt(0);
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+                if (item.getTextLinks() != null) {
+                    AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
+                }
+            }
+            if (isSensitive) {
+                showEditableText(
+                        mContext.getResources().getString(R.string.clipboard_asterisks), true);
+            } else {
+                showEditableText(item.getText(), false);
+            }
+            showShareChip(clipData);
+            accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
+        } else if (clipData.getItemAt(0).getUri() != null) {
+            if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
+                showShareChip(clipData);
+                accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
+            } else {
+                accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+            }
+        } else {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
+            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
+        }
+        Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
+        // Only show remote copy if it's available.
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.resolveActivity(
+                remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
+            mRemoteCopyChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_send_nearby_description));
+            mRemoteCopyChip.setVisibility(View.VISIBLE);
+            mRemoteCopyChip.setOnClickListener((v) -> {
+                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
+                mContext.startActivity(remoteCopyIntent);
+                animateOut();
+            });
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+        } else {
+            mRemoteCopyChip.setVisibility(View.GONE);
+        }
+        withWindowAttached(() -> {
+            if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
+                mView.post(this::animateIn);
+            }
+            mView.announceForAccessibility(accessibilityAnnouncement);
+        });
+        mTimeoutHandler.resetTimeout();
+    }
+
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setOnSessionCompleteListener(Runnable runnable) {
+        mOnSessionCompleteListener = runnable;
+    }
+
+    private void classifyText(ClipData.Item item, String source) {
+        ArrayList<RemoteAction> actions = new ArrayList<>();
+        for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
+            TextClassification classification = mTextClassifier.classifyText(
+                    item.getText(), link.getStart(), link.getEnd(), null);
+            actions.addAll(classification.getActions());
+        }
+        mView.post(() -> {
+            resetActionChips();
+            if (actions.size() > 0) {
+                mActionContainerBackground.setVisibility(View.VISIBLE);
+                for (RemoteAction action : actions) {
+                    Intent targetIntent = action.getActionIntent().getIntent();
+                    ComponentName component = targetIntent.getComponent();
+                    if (component != null && !TextUtils.equals(source,
+                            component.getPackageName())) {
+                        OverlayActionChip chip = constructActionChip(action);
+                        mActionContainer.addView(chip);
+                        mActionChips.add(chip);
+                        break; // only show at most one action chip
+                    }
+                }
+            }
+        });
+    }
+
+    private void showShareChip(ClipData clip) {
+        mShareChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+        mShareChip.setOnClickListener((v) -> shareContent(clip));
+    }
+
+    private OverlayActionChip constructActionChip(RemoteAction action) {
+        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_action_chip, mActionContainer, false);
+        chip.setText(action.getTitle());
+        chip.setContentDescription(action.getTitle());
+        chip.setIcon(action.getIcon(), false);
+        chip.setPendingIntent(action.getActionIntent(), () -> {
+            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+            animateOut();
+        });
+        chip.setAlpha(1);
+        return chip;
+    }
+
+    private void monitorOutsideTouches() {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
+        mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
+                Looper.getMainLooper()) {
+            @Override
+            public void onInputEvent(InputEvent event) {
+                if (event instanceof MotionEvent) {
+                    MotionEvent motionEvent = (MotionEvent) event;
+                    if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                        Region touchRegion = new Region();
+
+                        final Rect tmpRect = new Rect();
+                        mPreviewBorder.getBoundsOnScreen(tmpRect);
+                        tmpRect.inset(
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+                                        -SWIPE_PADDING_DP));
+                        touchRegion.op(tmpRect, Region.Op.UNION);
+                        mActionContainerBackground.getBoundsOnScreen(tmpRect);
+                        tmpRect.inset(
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
+                                        -SWIPE_PADDING_DP));
+                        touchRegion.op(tmpRect, Region.Op.UNION);
+                        mDismissButton.getBoundsOnScreen(tmpRect);
+                        touchRegion.op(tmpRect, Region.Op.UNION);
+                        if (!touchRegion.contains(
+                                (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
+                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+                            animateOut();
+                        }
+                    }
+                }
+                finishInputEvent(event, true /* handled */);
+            }
+        };
+    }
+
+    private void editImage(Uri uri) {
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+        mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
+        animateOut();
+    }
+
+    private void editText() {
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
+        mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
+        animateOut();
+    }
+
+    private void shareContent(ClipData clip) {
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+        mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
+        animateOut();
+    }
+
+    private void showSinglePreview(View v) {
+        mTextPreview.setVisibility(View.GONE);
+        mImagePreview.setVisibility(View.GONE);
+        mHiddenPreview.setVisibility(View.GONE);
+        v.setVisibility(View.VISIBLE);
+    }
+
+    private void showTextPreview(CharSequence text, TextView textView) {
+        showSinglePreview(textView);
+        final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
+        textView.setText(truncatedText);
+        updateTextSize(truncatedText, textView);
+
+        textView.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    if (right - left != oldRight - oldLeft) {
+                        updateTextSize(truncatedText, textView);
+                    }
+                });
+        mEditChip.setVisibility(View.GONE);
+    }
+
+    private void updateTextSize(CharSequence text, TextView textView) {
+        Paint paint = new Paint(textView.getPaint());
+        Resources res = textView.getResources();
+        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+            // If the text is a single word and would fit within the TextView at the min font size,
+            // find the biggest font size that will fit.
+            float fontSizePx = minFontSize;
+            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+                fontSizePx += FONT_SEARCH_STEP_PX;
+            }
+            // Need to turn off autosizing, otherwise setTextSize is a no-op.
+            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+            // It's possible to hit the max font size and not fill the width, so centering
+            // horizontally looks better in this case.
+            textView.setGravity(Gravity.CENTER);
+            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+        } else {
+            // Otherwise just stick with autosize.
+            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+        }
+    }
+
+    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+            float fontSizePx) {
+        paint.setTextSize(fontSizePx);
+        float size = paint.measureText(text.toString());
+        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+                - textView.getPaddingRight();
+        return size < availableWidth;
+    }
+
+    private static boolean isOneWord(CharSequence text) {
+        return text.toString().split("\\s+", 2).length == 1;
+    }
+
+    private void showEditableText(CharSequence text, boolean hidden) {
+        TextView textView = hidden ? mHiddenPreview : mTextPreview;
+        showTextPreview(text, textView);
+        View.OnClickListener listener = v -> editText();
+        setAccessibilityActionToEdit(textView);
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+            mEditChip.setVisibility(View.VISIBLE);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+            mEditChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_edit_text_description));
+            mEditChip.setOnClickListener(listener);
+        }
+        textView.setOnClickListener(listener);
+    }
+
+    private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
+        View.OnClickListener listener = v -> editImage(uri);
+        ContentResolver resolver = mContext.getContentResolver();
+        String mimeType = resolver.getType(uri);
+        boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
+        if (isSensitive) {
+            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+            showSinglePreview(mHiddenPreview);
+            if (isEditableImage) {
+                mHiddenPreview.setOnClickListener(listener);
+                setAccessibilityActionToEdit(mHiddenPreview);
+            }
+        } else if (isEditableImage) { // if the MIMEtype is image, try to load
+            try {
+                int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+                // The width of the view is capped, height maintains aspect ratio, so allow it to be
+                // taller if needed.
+                Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+                showSinglePreview(mImagePreview);
+                mImagePreview.setImageBitmap(thumbnail);
+                mImagePreview.setOnClickListener(listener);
+                setAccessibilityActionToEdit(mImagePreview);
+            } catch (IOException e) {
+                Log.e(TAG, "Thumbnail loading failed", e);
+                showTextPreview(
+                        mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                        mTextPreview);
+                isEditableImage = false;
+            }
+        } else {
+            showTextPreview(
+                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+                    mTextPreview);
+        }
+        if (isEditableImage && DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
+            mEditChip.setVisibility(View.VISIBLE);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+            mEditChip.setOnClickListener(listener);
+            mEditChip.setContentDescription(
+                    mContext.getString(R.string.clipboard_edit_image_description));
+        }
+        return isEditableImage;
+    }
+
+    private void setAccessibilityActionToEdit(View view) {
+        ViewCompat.replaceAccessibilityAction(view,
+                AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                mContext.getString(R.string.clipboard_edit), null);
+    }
+
+    private void animateIn() {
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
+        mEnterAnimator = getEnterAnimation();
+        mEnterAnimator.start();
+    }
+
+    private void animateOut() {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            return;
+        }
+        Animator anim = getExitAnimation();
+        anim.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (!mCancelled) {
+                    hideImmediate();
+                }
+            }
+        });
+        mExitAnimator = anim;
+        anim.start();
+    }
+
+    private Animator getEnterAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+        AnimatorSet enterAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(66);
+        rootAnim.addUpdateListener(animation -> {
+            mView.setAlpha(animation.getAnimatedFraction());
+        });
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(333);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionsScaleX);
+            mActionContainer.setScaleY(actionsScaleY);
+            mActionContainerBackground.setScaleX(actionsScaleX);
+            mActionContainerBackground.setScaleY(actionsScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(283);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        mActionContainer.setAlpha(0);
+        mPreviewBorder.setAlpha(0);
+        mClipboardPreview.setAlpha(0);
+        enterAnim.play(rootAnim).with(scaleAnim);
+        enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+        enterAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mView.setAlpha(1);
+                mTimeoutHandler.resetTimeout();
+            }
+        });
+        return enterAnim;
+    }
+
+    private Animator getExitAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+        AnimatorSet exitAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(100);
+        rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(250);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionScaleX);
+            mActionContainer.setScaleY(actionScaleY);
+            mActionContainerBackground.setScaleX(actionScaleX);
+            mActionContainerBackground.setScaleY(actionScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(166);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = 1 - animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        exitAnim.play(alphaAnim).with(scaleAnim);
+        exitAnim.play(rootAnim).after(150).after(alphaAnim);
+        return exitAnim;
+    }
+
+    private void hideImmediate() {
+        // Note this may be called multiple times if multiple dismissal events happen at the same
+        // time.
+        mTimeoutHandler.cancelTimeout();
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(decorView);
+        }
+        if (mCloseDialogsReceiver != null) {
+            mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
+            mCloseDialogsReceiver = null;
+        }
+        if (mScreenshotReceiver != null) {
+            mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
+            mScreenshotReceiver = null;
+        }
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+        }
+        if (mInputMonitor != null) {
+            mInputMonitor.dispose();
+            mInputMonitor = null;
+        }
+        if (mOnSessionCompleteListener != null) {
+            mOnSessionCompleteListener.run();
+        }
+    }
+
+    private void resetActionChips() {
+        for (OverlayActionChip chip : mActionChips) {
+            mActionContainer.removeView(chip);
+        }
+        mActionChips.clear();
+    }
+
+    private void reset() {
+        mView.setTranslationX(0);
+        mView.setAlpha(0);
+        mActionContainerBackground.setVisibility(View.GONE);
+        mShareChip.setVisibility(View.GONE);
+        mEditChip.setVisibility(View.GONE);
+        mRemoteCopyChip.setVisibility(View.GONE);
+        resetActionChips();
+        mTimeoutHandler.cancelTimeout();
+        mClipboardLogger.reset();
+    }
+
+    @MainThread
+    private void attachWindow() {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow() || mBlockAttach) {
+            return;
+        }
+        mBlockAttach = true;
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+        mView.requestApplyInsets();
+        decorView.getViewTreeObserver().addOnWindowAttachListener(
+                new ViewTreeObserver.OnWindowAttachListener() {
+                    @Override
+                    public void onWindowAttached() {
+                        mBlockAttach = false;
+                    }
+
+                    @Override
+                    public void onWindowDetached() {
+                    }
+                }
+        );
+    }
+
+    private void withWindowAttached(Runnable action) {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            mBlockAttach = false;
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() {
+                        }
+                    });
+        }
+    }
+
+    private void updateInsets(WindowInsets insets) {
+        int orientation = mContext.getResources().getConfiguration().orientation;
+        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
+        if (p == null) {
+            return;
+        }
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+        if (cutout == null) {
+            p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+        } else {
+            Insets waterfall = cutout.getWaterfallInsets();
+            if (orientation == ORIENTATION_PORTRAIT) {
+                p.setMargins(
+                        waterfall.left,
+                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(cutout.getSafeInsetBottom(),
+                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
+            } else {
+                p.setMargins(
+                        waterfall.left,
+                        waterfall.top,
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(navBarInsets.bottom, waterfall.bottom)));
+            }
+        }
+        mView.setLayoutParams(p);
+        mView.requestLayout();
+    }
+
+    private Display getDefaultDisplay() {
+        return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Updates the window focusability.  If the window is already showing, then it updates the
+     * window immediately, otherwise the layout params will be applied when the window is next
+     * shown.
+     */
+    private void setWindowFocusable(boolean focusable) {
+        int flags = mWindowLayoutParams.flags;
+        if (focusable) {
+            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        } else {
+            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        }
+        if (mWindowLayoutParams.flags == flags) {
+            return;
+        }
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+        }
+    }
+
+    static class ClipboardLogger {
+        private final UiEventLogger mUiEventLogger;
+        private boolean mGuarded = false;
+
+        ClipboardLogger(UiEventLogger uiEventLogger) {
+            mUiEventLogger = uiEventLogger;
+        }
+
+        void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
+            if (!mGuarded) {
+                mGuarded = true;
+                mUiEventLogger.log(event);
+            }
+        }
+
+        void reset() {
+            mGuarded = false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
rename to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
index 8b0b2a5..0d989a7 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
@@ -27,17 +27,17 @@
 import javax.inject.Inject;
 
 /**
- * A factory that churns out ClipboardOverlayControllers on demand.
+ * A factory that churns out ClipboardOverlayControllerLegacys on demand.
  */
 @SysUISingleton
-public class ClipboardOverlayControllerFactory {
+public class ClipboardOverlayControllerLegacyFactory {
 
     private final UiEventLogger mUiEventLogger;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final BroadcastSender mBroadcastSender;
 
     @Inject
-    public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher,
+    public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
             BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
         this.mBroadcastDispatcher = broadcastDispatcher;
         this.mBroadcastSender = broadcastSender;
@@ -45,10 +45,10 @@
     }
 
     /**
-     * One new ClipboardOverlayController, coming right up!
+     * One new ClipboardOverlayControllerLegacy, coming right up!
      */
-    public ClipboardOverlayController create(Context context) {
-        return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender,
+    public ClipboardOverlayControllerLegacy create(Context context) {
+        return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
                 new TimeoutHandler(context), mUiEventLogger);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
new file mode 100644
index 0000000..2d33157
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.clipboardoverlay;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+
+import com.android.systemui.R;
+import com.android.systemui.screenshot.DraggableConstraintLayout;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+import com.android.systemui.screenshot.OverlayActionChip;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the visual elements and animations for the clipboard overlay.
+ */
+public class ClipboardOverlayView extends DraggableConstraintLayout {
+
+    interface ClipboardOverlayCallbacks extends SwipeDismissCallbacks {
+        void onDismissButtonTapped();
+
+        void onRemoteCopyButtonTapped();
+
+        void onEditButtonTapped();
+
+        void onShareButtonTapped();
+
+        void onPreviewTapped();
+    }
+
+    private static final String TAG = "ClipboardView";
+
+    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
+    private static final int FONT_SEARCH_STEP_PX = 4;
+
+    private final DisplayMetrics mDisplayMetrics;
+    private final AccessibilityManager mAccessibilityManager;
+    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
+
+    private View mClipboardPreview;
+    private ImageView mImagePreview;
+    private TextView mTextPreview;
+    private TextView mHiddenPreview;
+    private View mPreviewBorder;
+    private OverlayActionChip mEditChip;
+    private OverlayActionChip mShareChip;
+    private OverlayActionChip mRemoteCopyChip;
+    private View mActionContainerBackground;
+    private View mDismissButton;
+    private LinearLayout mActionContainer;
+
+    public ClipboardOverlayView(Context context) {
+        this(context, null);
+    }
+
+    public ClipboardOverlayView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipboardOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDisplayMetrics = new DisplayMetrics();
+        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mActionContainerBackground =
+                requireNonNull(findViewById(R.id.actions_container_background));
+        mActionContainer = requireNonNull(findViewById(R.id.actions));
+        mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
+        mImagePreview = requireNonNull(findViewById(R.id.image_preview));
+        mTextPreview = requireNonNull(findViewById(R.id.text_preview));
+        mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
+        mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
+        mEditChip = requireNonNull(findViewById(R.id.edit_chip));
+        mShareChip = requireNonNull(findViewById(R.id.share_chip));
+        mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
+        mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+
+        mEditChip.setAlpha(1);
+        mShareChip.setAlpha(1);
+        mRemoteCopyChip.setAlpha(1);
+        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
+
+        mEditChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+        mRemoteCopyChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
+        mShareChip.setIcon(
+                Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+
+        mRemoteCopyChip.setContentDescription(
+                mContext.getString(R.string.clipboard_send_nearby_description));
+
+        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
+            int availableHeight = mTextPreview.getHeight()
+                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
+            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
+            return true;
+        });
+        super.onFinishInflate();
+    }
+
+    @Override
+    public void setCallbacks(SwipeDismissCallbacks callbacks) {
+        super.setCallbacks(callbacks);
+        ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
+        mEditChip.setOnClickListener(v -> clipboardCallbacks.onEditButtonTapped());
+        mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
+        mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
+        mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
+        mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+    }
+
+    void setEditAccessibilityAction(boolean editable) {
+        if (editable) {
+            ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    mContext.getString(R.string.clipboard_edit), null);
+        } else {
+            ViewCompat.replaceAccessibilityAction(mClipboardPreview,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    null, null);
+        }
+    }
+
+    void setInsets(WindowInsets insets, int orientation) {
+        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
+        if (p == null) {
+            return;
+        }
+        Rect margins = computeMargins(insets, orientation);
+        p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
+        setLayoutParams(p);
+        requestLayout();
+    }
+
+    boolean isInTouchRegion(int x, int y) {
+        Region touchRegion = new Region();
+        final Rect tmpRect = new Rect();
+
+        mPreviewBorder.getBoundsOnScreen(tmpRect);
+        tmpRect.inset(
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
+        mActionContainerBackground.getBoundsOnScreen(tmpRect);
+        tmpRect.inset(
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
+        mDismissButton.getBoundsOnScreen(tmpRect);
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
+        return touchRegion.contains(x, y);
+    }
+
+    void setRemoteCopyVisibility(boolean visible) {
+        if (visible) {
+            mRemoteCopyChip.setVisibility(View.VISIBLE);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
+        } else {
+            mRemoteCopyChip.setVisibility(View.GONE);
+        }
+    }
+
+    void showDefaultTextPreview() {
+        String copied = mContext.getString(R.string.clipboard_overlay_text_copied);
+        showTextPreview(copied, false);
+    }
+
+    void showTextPreview(CharSequence text, boolean hidden) {
+        TextView textView = hidden ? mHiddenPreview : mTextPreview;
+        showSinglePreview(textView);
+        textView.setText(text.subSequence(0, Math.min(500, text.length())));
+        updateTextSize(text, textView);
+        textView.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    if (right - left != oldRight - oldLeft) {
+                        updateTextSize(text, textView);
+                    }
+                });
+        mEditChip.setVisibility(View.GONE);
+    }
+
+    void showImagePreview(@Nullable Bitmap thumbnail) {
+        if (thumbnail == null) {
+            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
+            showSinglePreview(mHiddenPreview);
+        } else {
+            mImagePreview.setImageBitmap(thumbnail);
+            showSinglePreview(mImagePreview);
+        }
+    }
+
+    void showEditChip(String contentDescription) {
+        mEditChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+        mEditChip.setContentDescription(contentDescription);
+    }
+
+    void showShareChip() {
+        mShareChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+    }
+
+    void reset() {
+        setTranslationX(0);
+        setAlpha(0);
+        mActionContainerBackground.setVisibility(View.GONE);
+        mDismissButton.setVisibility(View.GONE);
+        mShareChip.setVisibility(View.GONE);
+        mEditChip.setVisibility(View.GONE);
+        mRemoteCopyChip.setVisibility(View.GONE);
+        setEditAccessibilityAction(false);
+        resetActionChips();
+    }
+
+    void resetActionChips() {
+        for (OverlayActionChip chip : mActionChips) {
+            mActionContainer.removeView(chip);
+        }
+        mActionChips.clear();
+    }
+
+    Animator getEnterAnimation() {
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+        AnimatorSet enterAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(66);
+        rootAnim.addUpdateListener(animation -> {
+            setAlpha(animation.getAnimatedFraction());
+        });
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(333);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionsScaleX);
+            mActionContainer.setScaleY(actionsScaleY);
+            mActionContainerBackground.setScaleX(actionsScaleX);
+            mActionContainerBackground.setScaleY(actionsScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(283);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        mActionContainer.setAlpha(0);
+        mPreviewBorder.setAlpha(0);
+        mClipboardPreview.setAlpha(0);
+        enterAnim.play(rootAnim).with(scaleAnim);
+        enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+        enterAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                setAlpha(1);
+            }
+        });
+        return enterAnim;
+    }
+
+    Animator getExitAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+        AnimatorSet exitAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(100);
+        rootAnim.addUpdateListener(anim -> setAlpha(1 - anim.getAnimatedFraction()));
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(250);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionScaleX);
+            mActionContainer.setScaleY(actionScaleY);
+            mActionContainerBackground.setScaleX(actionScaleX);
+            mActionContainerBackground.setScaleY(actionScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(166);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = 1 - animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        exitAnim.play(alphaAnim).with(scaleAnim);
+        exitAnim.play(rootAnim).after(150).after(alphaAnim);
+        return exitAnim;
+    }
+
+    void setActionChip(RemoteAction action, Runnable onFinish) {
+        mActionContainerBackground.setVisibility(View.VISIBLE);
+        OverlayActionChip chip = constructActionChip(action, onFinish);
+        mActionContainer.addView(chip);
+        mActionChips.add(chip);
+    }
+
+    private void showSinglePreview(View v) {
+        mTextPreview.setVisibility(View.GONE);
+        mImagePreview.setVisibility(View.GONE);
+        mHiddenPreview.setVisibility(View.GONE);
+        v.setVisibility(View.VISIBLE);
+    }
+
+    private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) {
+        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_action_chip, mActionContainer, false);
+        chip.setText(action.getTitle());
+        chip.setContentDescription(action.getTitle());
+        chip.setIcon(action.getIcon(), false);
+        chip.setPendingIntent(action.getActionIntent(), onFinish);
+        chip.setAlpha(1);
+        return chip;
+    }
+
+    private static void updateTextSize(CharSequence text, TextView textView) {
+        Paint paint = new Paint(textView.getPaint());
+        Resources res = textView.getResources();
+        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
+        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
+        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
+            // If the text is a single word and would fit within the TextView at the min font size,
+            // find the biggest font size that will fit.
+            float fontSizePx = minFontSize;
+            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
+                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
+                fontSizePx += FONT_SEARCH_STEP_PX;
+            }
+            // Need to turn off autosizing, otherwise setTextSize is a no-op.
+            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
+            // It's possible to hit the max font size and not fill the width, so centering
+            // horizontally looks better in this case.
+            textView.setGravity(Gravity.CENTER);
+            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
+        } else {
+            // Otherwise just stick with autosize.
+            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
+                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
+            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+        }
+    }
+
+    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
+            float fontSizePx) {
+        paint.setTextSize(fontSizePx);
+        float size = paint.measureText(text.toString());
+        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
+                - textView.getPaddingRight();
+        return size < availableWidth;
+    }
+
+    private static boolean isOneWord(CharSequence text) {
+        return text.toString().split("\\s+", 2).length == 1;
+    }
+
+    private static Rect computeMargins(WindowInsets insets, int orientation) {
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
+        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
+        if (cutout == null) {
+            return new Rect(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
+        } else {
+            Insets waterfall = cutout.getWaterfallInsets();
+            if (orientation == ORIENTATION_PORTRAIT) {
+                return new Rect(
+                        waterfall.left,
+                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(cutout.getSafeInsetBottom(),
+                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
+            } else {
+                return new Rect(
+                        waterfall.left,
+                        waterfall.top,
+                        waterfall.right,
+                        Math.max(imeInsets.bottom,
+                                Math.max(navBarInsets.bottom, waterfall.bottom)));
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
new file mode 100644
index 0000000..9dac9b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.clipboardoverlay;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.app.ICompatCameraControlCallback;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
+import com.android.systemui.screenshot.FloatingWindowUtil;
+
+import java.util.function.BiConsumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles attaching the window and the window insets for the clipboard overlay.
+ */
+public class ClipboardOverlayWindow extends PhoneWindow
+        implements ViewRootImpl.ActivityConfigCallback {
+    private static final String TAG = "ClipboardOverlayWindow";
+
+    private final Context mContext;
+    private final WindowManager mWindowManager;
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+
+    private boolean mKeyboardVisible;
+    private final int mOrientation;
+    private BiConsumer<WindowInsets, Integer> mOnKeyboardChangeListener;
+    private Runnable mOnOrientationChangeListener;
+
+    @Inject
+    ClipboardOverlayWindow(@OverlayWindowContext Context context) {
+        super(context);
+        mContext = context;
+        mOrientation = mContext.getResources().getConfiguration().orientation;
+
+        // Setup the window that we are going to use
+        requestFeature(Window.FEATURE_NO_TITLE);
+        requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
+        setBackgroundDrawableResource(android.R.color.transparent);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+        mWindowLayoutParams.setTitle("ClipboardOverlay");
+        setWindowManager(mWindowManager, null, null);
+        setWindowFocusable(false);
+    }
+
+    /**
+     * Set callbacks for keyboard state change and orientation change and attach the window
+     *
+     * @param onKeyboardChangeListener callback for IME visibility changes
+     * @param onOrientationChangeListener callback for device orientation changes
+     */
+    public void init(@NonNull BiConsumer<WindowInsets, Integer> onKeyboardChangeListener,
+            @NonNull Runnable onOrientationChangeListener) {
+        mOnKeyboardChangeListener = onKeyboardChangeListener;
+        mOnOrientationChangeListener = onOrientationChangeListener;
+
+        attach();
+        withWindowAttached(() -> {
+            WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+            mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime());
+            peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+                WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+                boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+                if (keyboardVisible != mKeyboardVisible) {
+                    mKeyboardVisible = keyboardVisible;
+                    mOnKeyboardChangeListener.accept(insets, mOrientation);
+                }
+            });
+            peekDecorView().getViewRootImpl().setActivityConfigCallback(this);
+        });
+    }
+
+    @Override // ViewRootImpl.ActivityConfigCallback
+    public void onConfigurationChanged(Configuration overrideConfig, int newDisplayId) {
+        if (mContext.getResources().getConfiguration().orientation != mOrientation) {
+            mOnOrientationChangeListener.run();
+        }
+    }
+
+    @Override // ViewRootImpl.ActivityConfigCallback
+    public void requestCompatCameraControl(boolean showControl, boolean transformationApplied,
+            ICompatCameraControlCallback callback) {
+        Log.w(TAG, "unexpected requestCompatCameraControl call");
+    }
+
+    void remove() {
+        final View decorView = peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(decorView);
+        }
+    }
+
+    WindowInsets getWindowInsets() {
+        return mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+    }
+
+    void withWindowAttached(Runnable action) {
+        View decorView = getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() {
+                        }
+                    });
+        }
+    }
+
+    @MainThread
+    private void attach() {
+        View decorView = getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            return;
+        }
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+    }
+
+    /**
+     * Updates the window focusability.  If the window is already showing, then it updates the
+     * window immediately, otherwise the layout params will be applied when the window is next
+     * shown.
+     */
+    private void setWindowFocusable(boolean focusable) {
+        int flags = mWindowLayoutParams.flags;
+        if (focusable) {
+            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        } else {
+            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        }
+        if (mWindowLayoutParams.flags == flags) {
+            return;
+        }
+        final View decorView = peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
new file mode 100644
index 0000000..2244813
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.clipboardoverlay.dagger;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.LayoutInflater;
+
+import com.android.systemui.R;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for {@link com.android.systemui.clipboardoverlay}. */
+@Module
+public interface ClipboardOverlayModule {
+
+    /**
+     *
+     */
+    @Provides
+    @OverlayWindowContext
+    static Context provideWindowContext(DisplayManager displayManager, Context context) {
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        return context.createWindowContext(display, TYPE_SCREENSHOT, null);
+    }
+
+    /**
+     *
+     */
+    @Provides
+    static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) {
+        return (ClipboardOverlayView) LayoutInflater.from(context).inflate(
+                R.layout.clipboard_overlay, null);
+    }
+
+    @Qualifier
+    @Documented
+    @Retention(RUNTIME)
+    @interface OverlayWindowContext {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index dc3dadb..d7638d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -33,6 +33,7 @@
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.classifier.FalsingModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.dagger.DemoModeModule;
@@ -118,6 +119,7 @@
             AssistModule.class,
             BiometricsModule.class,
             BouncerViewModule.class,
+            ClipboardOverlayModule.class,
             ClockModule.class,
             CoroutinesModule.class,
             DreamModule.class,
@@ -165,12 +167,16 @@
     @Binds
     abstract BootCompleteCache bindBootCompleteCache(BootCompleteCacheImpl bootCompleteCache);
 
-    /** */
+    /**
+     *
+     */
     @Binds
     public abstract ContextComponentHelper bindComponentHelper(
             ContextComponentResolver componentHelper);
 
-    /** */
+    /**
+     *
+     */
     @Binds
     public abstract NotificationRowBinder bindNotificationRowBinder(
             NotificationRowBinderImpl notificationRowBinder);
@@ -209,6 +215,7 @@
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
 
     // TODO: This should provided by the WM component
+
     /** Provides Optional of BubbleManager */
     @SysUISingleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 3dbadb0..7f08f86 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -105,10 +105,6 @@
      */
     public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
 
-    /** Whether UserSwitcherActivity should use modern architecture. */
-    public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
-            new ReleasedFlag(209, true);
-
     /**
      * Whether the user interactor and repository should use `UserSwitcherController`.
      *
@@ -309,6 +305,9 @@
     public static final UnreleasedFlag A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
             new UnreleasedFlag(1600);
 
+    // 1700 - clipboard
+    public static final UnreleasedFlag CLIPBOARD_OVERLAY_REFACTOR = new UnreleasedFlag(1700);
+
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
     // |                                                           |
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 01cd3e2..f663b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -19,7 +19,7 @@
 
 import android.content.Intent
 import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
@@ -67,19 +67,19 @@
      *
      * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
      * the affordance that was clicked
-     * @param animationController An optional controller for the activity-launch animation
+     * @param expandable An optional [Expandable] for the activity- or dialog-launch animation
      */
     fun onQuickAffordanceClicked(
         configKey: KClass<out KeyguardQuickAffordanceConfig>,
-        animationController: ActivityLaunchAnimator.Controller?,
+        expandable: Expandable?,
     ) {
         @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
-        when (val result = config.onQuickAffordanceClicked(animationController)) {
+        when (val result = config.onQuickAffordanceClicked(expandable)) {
             is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
                 launchQuickAffordance(
                     intent = result.intent,
                     canShowWhileLocked = result.canShowWhileLocked,
-                    animationController = animationController
+                    expandable = expandable,
                 )
             is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
         }
@@ -104,6 +104,7 @@
                 KeyguardQuickAffordanceModel.Visible(
                     configKey = configs[index]::class,
                     icon = visibleState.icon,
+                    toggle = visibleState.toggle,
                 )
             } else {
                 KeyguardQuickAffordanceModel.Hidden
@@ -114,7 +115,7 @@
     private fun launchQuickAffordance(
         intent: Intent,
         canShowWhileLocked: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
+        expandable: Expandable?,
     ) {
         @LockPatternUtils.StrongAuthTracker.StrongAuthFlags
         val strongAuthFlags =
@@ -130,13 +131,13 @@
             activityStarter.postStartActivityDismissingKeyguard(
                 intent,
                 0 /* delay */,
-                animationController
+                expandable?.activityLaunchController(),
             )
         } else {
             activityStarter.startActivity(
                 intent,
                 true /* dismissShade */,
-                animationController,
+                expandable?.activityLaunchController(),
                 true /* showOverLockscreenWhenLocked */,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index eb6bb92..e56b259 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -19,6 +19,7 @@
 
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import kotlin.reflect.KClass
 
 /**
@@ -35,5 +36,7 @@
         val configKey: KClass<out KeyguardQuickAffordanceConfig>,
         /** An icon for the affordance. */
         val icon: Icon,
+        /** The toggle state for the affordance. */
+        val toggle: KeyguardQuickAffordanceToggleState,
     ) : KeyguardQuickAffordanceModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 89604f0..8384260 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -20,7 +20,7 @@
 import android.content.Context
 import android.content.Intent
 import androidx.annotation.DrawableRes
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.ContentDescription
@@ -61,7 +61,7 @@
         }
 
     override fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?,
+        expandable: Expandable?,
     ): KeyguardQuickAffordanceConfig.OnClickedResult {
         return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
             intent =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 8e1c6b7..95027d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,8 +18,9 @@
 package com.android.systemui.keyguard.domain.quickaffordance
 
 import android.content.Intent
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
@@ -27,9 +28,7 @@
 
     val state: Flow<State>
 
-    fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?
-    ): OnClickedResult
+    fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
 
     /**
      * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
@@ -44,6 +43,9 @@
         data class Visible(
             /** An icon for the affordance. */
             val icon: Icon,
+            /** The toggle state for the affordance. */
+            val toggle: KeyguardQuickAffordanceToggleState =
+                KeyguardQuickAffordanceToggleState.NotSupported,
         ) : State()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d97deaf..502a607 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -18,7 +18,7 @@
 package com.android.systemui.keyguard.domain.quickaffordance
 
 import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.ContentDescription
@@ -66,7 +66,7 @@
     }
 
     override fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?,
+        expandable: Expandable?,
     ): KeyguardQuickAffordanceConfig.OnClickedResult {
         return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
             intent = controller.intent,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 9196b09..a24a0d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -23,7 +23,7 @@
 import android.service.quickaccesswallet.QuickAccessWalletClient
 import android.util.Log
 import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.ContentDescription
@@ -84,11 +84,11 @@
     }
 
     override fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?,
+        expandable: Expandable?,
     ): KeyguardQuickAffordanceConfig.OnClickedResult {
         walletController.startQuickAccessUiIntent(
             activityStarter,
-            animationController,
+            expandable?.activityLaunchController(),
             /* hasCard= */ true,
         )
         return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
new file mode 100644
index 0000000..55d38a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.quickaffordance
+
+/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */
+sealed class KeyguardQuickAffordanceToggleState {
+    /** Toggling is not supported. */
+    object NotSupported : KeyguardQuickAffordanceToggleState()
+    /** The quick affordance is on. */
+    object On : KeyguardQuickAffordanceToggleState()
+    /** The quick affordance is off. */
+    object Off : KeyguardQuickAffordanceToggleState()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 65b85c0..2c99ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -29,7 +29,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
@@ -238,14 +238,26 @@
 
         IconViewBinder.bind(viewModel.icon, view)
 
+        view.isActivated = viewModel.isActivated
         view.drawable.setTint(
             Utils.getColorAttrDefaultColor(
                 view.context,
-                com.android.internal.R.attr.textColorPrimary
+                if (viewModel.isActivated) {
+                    com.android.internal.R.attr.textColorPrimaryInverse
+                } else {
+                    com.android.internal.R.attr.textColorPrimary
+                },
             )
         )
         view.backgroundTintList =
-            Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
+            Utils.getColorAttr(
+                view.context,
+                if (viewModel.isActivated) {
+                    com.android.internal.R.attr.colorAccentPrimary
+                } else {
+                    com.android.internal.R.attr.colorSurface
+                }
+            )
 
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
@@ -268,7 +280,7 @@
                 viewModel.onClicked(
                     KeyguardQuickAffordanceViewModel.OnClickedParameters(
                         configKey = viewModel.configKey,
-                        animationController = ActivityLaunchAnimator.Controller.fromView(view),
+                        expandable = Expandable.fromView(view),
                     )
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 970ee4c..535ca72 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -119,10 +120,11 @@
                     onClicked = { parameters ->
                         quickAffordanceInteractor.onQuickAffordanceClicked(
                             configKey = parameters.configKey,
-                            animationController = parameters.animationController,
+                            expandable = parameters.expandable,
                         )
                     },
                     isClickable = isClickable,
+                    isActivated = toggle is KeyguardQuickAffordanceToggleState.On,
                 )
             is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 0971f13..bf598ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
 import kotlin.reflect.KClass
@@ -30,9 +30,10 @@
     val icon: Icon = Icon.Resource(res = 0, contentDescription = null),
     val onClicked: (OnClickedParameters) -> Unit = {},
     val isClickable: Boolean = false,
+    val isActivated: Boolean = false,
 ) {
     data class OnClickedParameters(
         val configKey: KClass<out KeyguardQuickAffordanceConfig>,
-        val animationController: ActivityLaunchAnimator.Controller?,
+        val expandable: Expandable?,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50a10bc..6da8c69 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -114,6 +114,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
@@ -211,6 +212,7 @@
     private final NotificationShadeDepthController mNotificationShadeDepthController;
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
     private final UserContextProvider mUserContextProvider;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final RegionSamplingHelper mRegionSamplingHelper;
     private final int mNavColorSampleMargin;
     private NavigationBarFrame mFrame;
@@ -451,6 +453,28 @@
                 }
             };
 
+    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+            new WakefulnessLifecycle.Observer() {
+                private void notifyScreenStateChanged(boolean isScreenOn) {
+                    notifyNavigationBarScreenOn();
+                    mView.onScreenStateChanged(isScreenOn);
+                }
+
+                @Override
+                public void onStartedWakingUp() {
+                    notifyScreenStateChanged(true);
+                    if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
+                        mRegionSamplingHelper.start(mSamplingBounds);
+                    }
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    notifyScreenStateChanged(false);
+                    mRegionSamplingHelper.stop();
+                }
+            };
+
     @Inject
     NavigationBar(
             NavigationBarView navigationBarView,
@@ -491,7 +515,8 @@
             NavigationBarTransitions navigationBarTransitions,
             EdgeBackGestureHandler edgeBackGestureHandler,
             Optional<BackAnimation> backAnimation,
-            UserContextProvider userContextProvider) {
+            UserContextProvider userContextProvider,
+            WakefulnessLifecycle wakefulnessLifecycle) {
         super(navigationBarView);
         mFrame = navigationBarFrame;
         mContext = context;
@@ -529,6 +554,7 @@
         mTelecomManagerOptional = telecomManagerOptional;
         mInputMethodManager = inputMethodManager;
         mUserContextProvider = userContextProvider;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
 
         mNavColorSampleMargin = getResources()
                 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -682,11 +708,10 @@
         prepareNavigationBarView();
         checkNavBarModes();
 
-        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_SCREEN_ON);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
         mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
                 Handler.getMain(), UserHandle.ALL);
+        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         notifyNavigationBarScreenOn();
 
         mOverviewProxyService.addCallback(mOverviewProxyListener);
@@ -737,6 +762,7 @@
         getBarTransitions().destroy();
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
+        mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
         if (mOrientationHandle != null) {
             resetSecondaryHandle();
             getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
@@ -1619,19 +1645,6 @@
                 return;
             }
             String action = intent.getAction();
-            if (Intent.ACTION_SCREEN_OFF.equals(action)
-                    || Intent.ACTION_SCREEN_ON.equals(action)) {
-                notifyNavigationBarScreenOn();
-                boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(action);
-                mView.onScreenStateChanged(isScreenOn);
-                if (isScreenOn) {
-                    if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
-                        mRegionSamplingHelper.start(mSamplingBounds);
-                    }
-                } else {
-                    mRegionSamplingHelper.stop();
-                }
-            }
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 // The accessibility settings may be different for the new user
                 updateAccessibilityStateFlags();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 029cf68..3fd1aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -21,6 +21,7 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
 import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
 
 import android.content.ContentResolver;
@@ -141,7 +142,13 @@
     public void onConfigChanged(Configuration newConfig) {
         boolean isOldConfigTablet = mIsTablet;
         mIsTablet = isTablet(mContext);
+        boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+        // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+        Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+                + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
+                + " willApplyConfigToNavbars=" + willApplyConfig
+                + " navBarCount=" + mNavigationBars.size());
         if (mTaskbarDelegate.isInitialized()) {
             mTaskbarDelegate.onConfigurationChanged(newConfig);
         }
@@ -150,7 +157,7 @@
             return;
         }
 
-        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+        if (willApplyConfig) {
             for (int i = 0; i < mNavigationBars.size(); i++) {
                 recreateNavigationBar(mNavigationBars.keyAt(i));
             }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index a8799c7..709467f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -113,7 +113,7 @@
     private static final int MAX_NUM_LOGGED_GESTURES = 10;
 
     static final boolean DEBUG_MISSING_GESTURE = false;
-    static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
+    public static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture";
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
             new ISystemGestureExclusionListener.Stub() {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index fae0b50..1da866e 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -46,6 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -78,6 +79,7 @@
 
     private final PowerManager mPowerManager;
     private final WarningsUI mWarnings;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
     private InattentiveSleepWarningView mOverlayView;
     private final Configuration mLastConfiguration = new Configuration();
     private int mPlugType = 0;
@@ -107,11 +109,24 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final CommandQueue mCommandQueue;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+    private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onStartedWakingUp() {
+                    mScreenOffTime = -1;
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    mScreenOffTime = SystemClock.elapsedRealtime();
+                }
+            };
 
     @Inject
     public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
             CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+            WakefulnessLifecycle wakefulnessLifecycle,
             PowerManager powerManager) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -120,6 +135,7 @@
         mWarnings = warningsUI;
         mEnhancedEstimates = enhancedEstimates;
         mPowerManager = powerManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
     }
 
     public void start() {
@@ -138,6 +154,7 @@
                 false, obs, UserHandle.USER_ALL);
         updateBatteryWarningLevels();
         mReceiver.init();
+        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
 
         // Check to see if we need to let the user know that the phone previously shut down due
         // to the temperature being too high.
@@ -233,8 +250,6 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-            filter.addAction(Intent.ACTION_SCREEN_OFF);
-            filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_USER_SWITCHED);
             mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
             // Force get initial values. Relying on Sticky behavior until API for getting info.
@@ -317,10 +332,6 @@
                             plugged, bucket);
                 });
 
-            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                mScreenOffTime = SystemClock.elapsedRealtime();
-            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
-                mScreenOffTime = -1;
             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 mWarnings.userSwitched();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 11d9555..d3c06f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import com.android.settingslib.Utils
-import com.android.settingslib.drawable.UserIconDrawable
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
@@ -250,22 +249,19 @@
         status: UserSwitcherStatusModel.Enabled
     ): FooterActionsButtonViewModel {
         val icon = status.currentUserImage!!
-        val iconTint =
-            if (status.isGuestUser && icon !is UserIconDrawable) {
-                Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground)
-            } else {
-                null
-            }
 
         return FooterActionsButtonViewModel(
             id = R.id.multi_user_switch,
-            Icon.Loaded(
-                icon,
-                ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)),
-            ),
-            iconTint,
-            R.drawable.qs_footer_action_circle,
-            this::onUserSwitcherClicked,
+            icon =
+                Icon.Loaded(
+                    icon,
+                    ContentDescription.Loaded(
+                        userSwitcherContentDescription(status.currentUserName)
+                    ),
+                ),
+            iconTint = null,
+            background = R.drawable.qs_footer_action_circle,
+            onClick = this::onUserSwitcherClicked,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index 950806d..ead3b7b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -49,7 +49,6 @@
     private final SwipeDismissHandler mSwipeDismissHandler;
     private final GestureDetector mSwipeDetector;
     private View mActionsContainer;
-    private View mActionsContainerBackground;
     private SwipeDismissCallbacks mCallbacks;
     private final DisplayMetrics mDisplayMetrics;
 
@@ -111,6 +110,9 @@
                     }
                 });
         mSwipeDetector.setIsLongpressEnabled(false);
+
+        mCallbacks = new SwipeDismissCallbacks() {
+        }; // default to unimplemented callbacks
     }
 
     public void setCallbacks(SwipeDismissCallbacks callbacks) {
@@ -119,16 +121,13 @@
 
     @Override
     public boolean onInterceptHoverEvent(MotionEvent event) {
-        if (mCallbacks != null) {
-            mCallbacks.onInteraction();
-        }
+        mCallbacks.onInteraction();
         return super.onInterceptHoverEvent(event);
     }
 
     @Override // View
     protected void onFinishInflate() {
         mActionsContainer = findViewById(R.id.actions_container);
-        mActionsContainerBackground = findViewById(R.id.actions_container_background);
     }
 
     @Override
@@ -186,6 +185,13 @@
         inoutInfo.touchableRegion.set(r);
     }
 
+    private int getBackgroundRight() {
+        // background expected to be null in testing.
+        // animation may have unexpected behavior if view is not present
+        View background = findViewById(R.id.actions_container_background);
+        return background == null ? 0 : background.getRight();
+    }
+
     /**
      * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not
      * met
@@ -213,8 +219,6 @@
             mGestureDetector = new GestureDetector(context, gestureListener);
             mDisplayMetrics = new DisplayMetrics();
             context.getDisplay().getRealMetrics(mDisplayMetrics);
-            mCallbacks = new SwipeDismissCallbacks() {
-            }; // default to unimplemented callbacks
         }
 
         @Override
@@ -230,7 +234,9 @@
                     return true;
                 }
                 if (isPastDismissThreshold()) {
-                    dismiss();
+                    ValueAnimator anim = createSwipeDismissAnimation();
+                    mCallbacks.onSwipeDismissInitiated(anim);
+                    dismiss(anim);
                 } else {
                     // if we've moved, but not past the threshold, start the return animation
                     if (DEBUG_DISMISS) {
@@ -295,10 +301,7 @@
         }
 
         void dismiss() {
-            float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS);
-            ValueAnimator anim = createSwipeDismissAnimation(velocityPxPerMs);
-            mCallbacks.onSwipeDismissInitiated(anim);
-            dismiss(anim);
+            dismiss(createSwipeDismissAnimation());
         }
 
         private void dismiss(ValueAnimator animator) {
@@ -323,6 +326,11 @@
             mDismissAnimation.start();
         }
 
+        private ValueAnimator createSwipeDismissAnimation() {
+            float velocityPxPerMs = FloatingWindowUtil.dpToPx(mDisplayMetrics, VELOCITY_DP_PER_MS);
+            return createSwipeDismissAnimation(velocityPxPerMs);
+        }
+
         private ValueAnimator createSwipeDismissAnimation(float velocity) {
             // velocity is measured in pixels per millisecond
             velocity = Math.min(3, Math.max(1, velocity));
@@ -337,7 +345,7 @@
             if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) {
                 finalX = mDisplayMetrics.widthPixels;
             } else {
-                finalX = -1 * mActionsContainerBackground.getRight();
+                finalX = -1 * getBackgroundRight();
             }
             float distance = Math.abs(finalX - startX);
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 704e115..6d5121a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -28,6 +28,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
 import static com.android.systemui.screenshot.LogConfig.logTag;
 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
 
 import static java.util.Objects.requireNonNull;
 
@@ -331,9 +332,7 @@
             if (DEBUG_UI) {
                 Log.d(TAG, "Corner timeout hit");
             }
-            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT, 0,
-                    mPackageName);
-            ScreenshotController.this.dismissScreenshot(false);
+            dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
         });
 
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -361,8 +360,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
-                    mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER);
-                    dismissScreenshot(false);
+                    dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
                 }
             }
         };
@@ -410,24 +408,20 @@
     /**
      * Clears current screenshot
      */
-    void dismissScreenshot(boolean immediate) {
+    void dismissScreenshot(ScreenshotEvent event) {
         if (DEBUG_DISMISS) {
-            Log.d(TAG, "dismissScreenshot(immediate=" + immediate + ")");
+            Log.d(TAG, "dismissScreenshot");
         }
         // If we're already animating out, don't restart the animation
-        // (but do obey an immediate dismissal)
-        if (!immediate && mScreenshotView.isDismissing()) {
+        if (mScreenshotView.isDismissing()) {
             if (DEBUG_DISMISS) {
                 Log.v(TAG, "Already dismissing, ignoring duplicate command");
             }
             return;
         }
+        mUiEventLogger.log(event, 0, mPackageName);
         mScreenshotHandler.cancelTimeout();
-        if (immediate) {
-            finishDismiss();
-        } else {
-            mScreenshotView.animateDismissal();
-        }
+        mScreenshotView.animateDismissal();
     }
 
     boolean isPendingSharedTransition() {
@@ -500,7 +494,7 @@
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
                 }
-                dismissScreenshot(false);
+                dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
                 return true;
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index a4a59ce..2176825 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -89,8 +89,7 @@
                     Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
                 }
                 if (!mScreenshot.isPendingSharedTransition()) {
-                    mUiEventLogger.log(SCREENSHOT_DISMISSED_OTHER);
-                    mScreenshot.dismissScreenshot(false);
+                    mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index c290ce2..184dc25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -189,7 +189,6 @@
     protected NotificationPresenter mPresenter;
     protected ContentObserver mLockscreenSettingsObserver;
     protected ContentObserver mSettingsObserver;
-    private boolean mHideSilentNotificationsOnLockscreen;
 
     @Inject
     public NotificationLockscreenUserManagerImpl(Context context,
@@ -266,12 +265,6 @@
                 UserHandle.USER_ALL);
 
         mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS),
-                true,
-                mLockscreenSettingsObserver,
-                UserHandle.USER_ALL);
-
-        mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
                 mSettingsObserver);
 
@@ -340,9 +333,6 @@
         final boolean allowedByDpm = (dpmFlags
                 & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
 
-        mHideSilentNotificationsOnLockscreen = mSecureSettings.getIntForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1, mCurrentUserId) == 0;
-
         setShowLockscreenNotifications(show && allowedByDpm);
 
         if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 6f41425..9a7610d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -114,7 +114,18 @@
      */
     public void unbindHeadsUpView(NotificationEntry entry) {
         abortBindCallback(entry);
-        mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
+
+        // params may be null if the notification was already removed from the collection but we let
+        // it stick around during a launch animation. In this case, the heads up view has already
+        // been unbound, so we don't need to unbind it.
+        // TODO(b/253081345): Change this back to getStageParams and remove null check.
+        RowContentBindParams params = mStage.tryGetStageParams(entry);
+        if (params == null) {
+            mLogger.entryBindStageParamsNullOnUnbind(entry);
+            return;
+        }
+
+        params.markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
         mLogger.entryContentViewMarkedFreeable(entry);
         mStage.requestRebind(entry, e -> mLogger.entryUnbound(e));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index d1feaa0..5dbec8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -47,6 +47,14 @@
             "start unbinding heads up entry $str1 "
         })
     }
+
+    fun entryBindStageParamsNullOnUnbind(entry: NotificationEntry) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+        }, {
+            "heads up entry bind stage params null on unbind $str1 "
+        })
+    }
 }
 
 private const val TAG = "HeadsUpViewBinder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
index 7c41800..d626c18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -64,7 +65,7 @@
      * Get the stage parameters for the entry. Clients should use this to modify how the stage
      * handles the notification content.
      */
-    public final Params getStageParams(@NonNull NotificationEntry entry) {
+    public final @NonNull Params getStageParams(@NonNull NotificationEntry entry) {
         Params params = mContentParams.get(entry);
         if (params == null) {
             // TODO: This should throw an exception but there are some cases of re-entrant calls
@@ -79,6 +80,17 @@
         return params;
     }
 
+    // TODO(b/253081345): Remove this method.
+    /**
+     * Get the stage parameters for the entry, or null if there are no stage parameters for the
+     * entry.
+     *
+     * @see #getStageParams(NotificationEntry)
+     */
+    public final @Nullable Params tryGetStageParams(@NonNull NotificationEntry entry) {
+        return mContentParams.get(entry);
+    }
+
     /**
      * Create a params entry for the notification for this stage.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 5b2d695..2f0ebf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -35,8 +35,8 @@
     protected val controller: UserSwitcherController,
 ) : BaseAdapter() {
 
-    protected open val users: ArrayList<UserRecord>
-        get() = controller.users
+    protected open val users: List<UserRecord>
+        get() = controller.users.filter { !controller.isKeyguardShowing || !it.isRestricted }
 
     init {
         controller.addAdapter(WeakReference(this))
@@ -112,6 +112,7 @@
                     item.isGuest,
                     item.isAddSupervisedUser,
                     isTablet,
+                    item.isManageUsers,
                 )
             return checkNotNull(context.getDrawable(iconRes))
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 494a4bb..c150654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -53,6 +53,7 @@
 import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.inject.Inject;
 
@@ -456,7 +457,7 @@
         }
 
         void refreshUserOrder() {
-            ArrayList<UserRecord> users = super.getUsers();
+            List<UserRecord> users = super.getUsers();
             mUsersOrdered = new ArrayList<>(users.size());
             for (int i = 0; i < users.size(); i++) {
                 UserRecord record = users.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
index af39eee..935fc7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
@@ -249,7 +249,7 @@
 
     override fun startActivity(intent: Intent) {
         if (useInteractor) {
-            activityStarter.startActivity(intent, /* dismissShade= */ false)
+            activityStarter.startActivity(intent, /* dismissShade= */ true)
         } else {
             _oldImpl.startActivity(intent)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
index 46d2f3a..c294c37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
@@ -49,6 +49,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.users.UserCreatingDialog;
 import com.android.systemui.GuestResetOrExitSessionReceiver;
 import com.android.systemui.GuestResumeSessionReceiver;
@@ -399,6 +400,16 @@
                 records.add(userRecord);
             }
 
+            if (canManageUsers()) {
+                records.add(LegacyUserDataHelper.createRecord(
+                        mContext,
+                        KeyguardUpdateMonitor.getCurrentUser(),
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                        /* isRestricted= */ false,
+                        /* isSwitchToEnabled= */ true
+                ));
+            }
+
             mUiExecutor.execute(() -> {
                 if (records != null) {
                     mUsers = records;
@@ -438,6 +449,14 @@
                 && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
     }
 
+    @VisibleForTesting
+    boolean canManageUsers() {
+        UserInfo currentUser = mUserTracker.getUserInfo();
+        return mUserSwitcherEnabled
+                && ((currentUser != null && currentUser.isAdmin())
+                || mAddUsersFromLockScreen.getValue());
+    }
+
     private boolean createIsRestricted() {
         return !mAddUsersFromLockScreen.getValue();
     }
@@ -525,6 +544,8 @@
             showAddUserDialog(dialogShower);
         } else if (record.isAddSupervisedUser) {
             startSupervisedUserActivity();
+        } else if (record.isManageUsers) {
+            startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
         } else {
             onUserListItemClicked(record.info.id, record, dialogShower);
         }
@@ -984,7 +1005,7 @@
 
     @Override
     public void startActivity(Intent intent) {
-        mActivityStarter.startActivity(intent, true);
+        mActivityStarter.startActivity(intent, /* dismissShade= */ true);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 108ab43..7f1195b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -16,426 +16,41 @@
 
 package com.android.systemui.user
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.LayerDrawable
 import android.os.Bundle
-import android.os.UserManager
-import android.provider.Settings
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.MotionEvent
 import android.view.View
-import android.view.ViewGroup
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.ImageView
-import android.widget.TextView
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
-import androidx.constraintlayout.helper.widget.Flow
 import androidx.lifecycle.ViewModelProvider
-import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.util.UserIcons
-import com.android.settingslib.Utils
-import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
 import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import dagger.Lazy
 import javax.inject.Inject
-import kotlin.math.ceil
-
-private const val USER_VIEW = "user_view"
 
 /** Support a fullscreen user switcher */
 open class UserSwitcherActivity
 @Inject
 constructor(
-    private val userSwitcherController: UserSwitcherController,
-    private val broadcastDispatcher: BroadcastDispatcher,
     private val falsingCollector: FalsingCollector,
-    private val falsingManager: FalsingManager,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
-    private val flags: FeatureFlags,
     private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
 ) : ComponentActivity() {
 
-    private lateinit var parent: UserSwitcherRootView
-    private lateinit var broadcastReceiver: BroadcastReceiver
-    private var popupMenu: UserSwitcherPopupMenu? = null
-    private lateinit var addButton: View
-    private var addUserRecords = mutableListOf<UserRecord>()
-    private val onBackCallback = OnBackInvokedCallback { finish() }
-    private val userSwitchedCallback: UserTracker.Callback =
-        object : UserTracker.Callback {
-            override fun onUserChanged(newUser: Int, userContext: Context) {
-                finish()
-            }
-        }
-    // When the add users options become available, insert another option to manage users
-    private val manageUserRecord =
-        UserRecord(
-            null /* info */,
-            null /* picture */,
-            false /* isGuest */,
-            false /* isCurrent */,
-            false /* isAddUser */,
-            false /* isRestricted */,
-            false /* isSwitchToEnabled */,
-            false /* isAddSupervisedUser */
-        )
-
-    private val adapter: UserAdapter by lazy { UserAdapter() }
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        createActivity()
-    }
-
-    @VisibleForTesting
-    fun createActivity() {
         setContentView(R.layout.user_switcher_fullscreen)
         window.decorView.systemUiVisibility =
             (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
-        if (isUsingModernArchitecture()) {
-            Log.d(TAG, "Using modern architecture.")
-            val viewModel =
-                ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
-            UserSwitcherViewBinder.bind(
-                view = requireViewById(R.id.user_switcher_root),
-                viewModel = viewModel,
-                lifecycleOwner = this,
-                layoutInflater = layoutInflater,
-                falsingCollector = falsingCollector,
-                onFinish = this::finish,
-            )
-            return
-        } else {
-            Log.d(TAG, "Not using modern architecture.")
-        }
-
-        parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
-
-        parent.touchHandler =
-            object : Gefingerpoken {
-                override fun onTouchEvent(ev: MotionEvent?): Boolean {
-                    falsingCollector.onTouchEvent(ev)
-                    return false
-                }
-            }
-
-        requireViewById<View>(R.id.cancel).apply { setOnClickListener { _ -> finish() } }
-
-        addButton =
-            requireViewById<View>(R.id.add).apply { setOnClickListener { _ -> showPopupMenu() } }
-
-        onBackInvokedDispatcher.registerOnBackInvokedCallback(
-            OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-            onBackCallback
+        val viewModel =
+            ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
+        UserSwitcherViewBinder.bind(
+            view = requireViewById(R.id.user_switcher_root),
+            viewModel = viewModel,
+            lifecycleOwner = this,
+            layoutInflater = layoutInflater,
+            falsingCollector = falsingCollector,
+            onFinish = this::finish,
         )
-
-        userSwitcherController.init(parent)
-        initBroadcastReceiver()
-
-        parent.post { buildUserViews() }
-        userTracker.addCallback(userSwitchedCallback, mainExecutor)
-    }
-
-    private fun showPopupMenu() {
-        val items = mutableListOf<UserRecord>()
-        addUserRecords.forEach { items.add(it) }
-
-        var popupMenuAdapter =
-            ItemAdapter(
-                this,
-                R.layout.user_switcher_fullscreen_popup_item,
-                layoutInflater,
-                { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item, true) },
-                { item: UserRecord ->
-                    adapter.findUserIcon(item, true).mutate().apply {
-                        setTint(
-                            resources.getColor(
-                                R.color.user_switcher_fullscreen_popup_item_tint,
-                                getTheme()
-                            )
-                        )
-                    }
-                }
-            )
-        popupMenuAdapter.addAll(items)
-
-        popupMenu =
-            UserSwitcherPopupMenu(this).apply {
-                setAnchorView(addButton)
-                setAdapter(popupMenuAdapter)
-                setOnItemClickListener { parent: AdapterView<*>, view: View, pos: Int, id: Long ->
-                    if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
-                        return@setOnItemClickListener
-                    }
-                    // -1 for the header
-                    val item = popupMenuAdapter.getItem(pos - 1)
-                    if (item == manageUserRecord) {
-                        val i = Intent().setAction(Settings.ACTION_USER_SETTINGS)
-                        this@UserSwitcherActivity.startActivity(i)
-                    } else {
-                        adapter.onUserListItemClicked(item)
-                    }
-
-                    dismiss()
-                    popupMenu = null
-
-                    if (!item.isAddUser) {
-                        this@UserSwitcherActivity.finish()
-                    }
-                }
-
-                show()
-            }
-    }
-
-    private fun buildUserViews() {
-        var count = 0
-        var start = 0
-        for (i in 0 until parent.getChildCount()) {
-            if (parent.getChildAt(i).getTag() == USER_VIEW) {
-                if (count == 0) start = i
-                count++
-            }
-        }
-        parent.removeViews(start, count)
-        addUserRecords.clear()
-        val flow = requireViewById<Flow>(R.id.flow)
-        val totalWidth = parent.width
-        val userViewCount = adapter.getTotalUserViews()
-        val maxColumns = getMaxColumns(userViewCount)
-        val horizontalGap =
-            resources.getDimensionPixelSize(R.dimen.user_switcher_fullscreen_horizontal_gap)
-        val totalWidthOfHorizontalGap = (maxColumns - 1) * horizontalGap
-        val maxWidgetDiameter = (totalWidth - totalWidthOfHorizontalGap) / maxColumns
-
-        flow.setMaxElementsWrap(maxColumns)
-
-        for (i in 0 until adapter.getCount()) {
-            val item = adapter.getItem(i)
-            if (adapter.doNotRenderUserView(item)) {
-                addUserRecords.add(item)
-            } else {
-                val userView = adapter.getView(i, null, parent)
-                userView.requireViewById<ImageView>(R.id.user_switcher_icon).apply {
-                    val lp = layoutParams
-                    if (maxWidgetDiameter < lp.width) {
-                        lp.width = maxWidgetDiameter
-                        lp.height = maxWidgetDiameter
-                        layoutParams = lp
-                    }
-                }
-
-                userView.setId(View.generateViewId())
-                parent.addView(userView)
-
-                // Views must have an id and a parent in order for Flow to lay them out
-                flow.addView(userView)
-
-                userView.setOnClickListener { v ->
-                    if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) {
-                        return@setOnClickListener
-                    }
-
-                    if (!item.isCurrent || item.isGuest) {
-                        adapter.onUserListItemClicked(item)
-                    }
-                }
-            }
-        }
-
-        if (!addUserRecords.isEmpty()) {
-            addUserRecords.add(manageUserRecord)
-            addButton.visibility = View.VISIBLE
-        } else {
-            addButton.visibility = View.GONE
-        }
-    }
-
-    override fun onBackPressed() {
-        if (isUsingModernArchitecture()) {
-            return super.onBackPressed()
-        }
-
-        finish()
-    }
-
-    override fun onDestroy() {
-        super.onDestroy()
-        if (isUsingModernArchitecture()) {
-            return
-        }
-        destroyActivity()
-    }
-
-    @VisibleForTesting
-    fun destroyActivity() {
-        onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackCallback)
-        broadcastDispatcher.unregisterReceiver(broadcastReceiver)
-        userTracker.removeCallback(userSwitchedCallback)
-    }
-
-    private fun initBroadcastReceiver() {
-        broadcastReceiver =
-            object : BroadcastReceiver() {
-                override fun onReceive(context: Context, intent: Intent) {
-                    val action = intent.getAction()
-                    if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                        finish()
-                    }
-                }
-            }
-
-        val filter = IntentFilter()
-        filter.addAction(Intent.ACTION_SCREEN_OFF)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
-    }
-
-    @VisibleForTesting
-    fun getMaxColumns(userCount: Int): Int {
-        return if (userCount < 5) 4 else ceil(userCount / 2.0).toInt()
-    }
-
-    private fun isUsingModernArchitecture(): Boolean {
-        return flags.isEnabled(Flags.MODERN_USER_SWITCHER_ACTIVITY)
-    }
-
-    /** Provides views to populate the option menu. */
-    private class ItemAdapter(
-        val parentContext: Context,
-        val resource: Int,
-        val layoutInflater: LayoutInflater,
-        val textGetter: (UserRecord) -> String,
-        val iconGetter: (UserRecord) -> Drawable
-    ) : ArrayAdapter<UserRecord>(parentContext, resource) {
-
-        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
-            val item = getItem(position)
-            val view = convertView ?: layoutInflater.inflate(resource, parent, false)
-
-            view.requireViewById<ImageView>(R.id.icon).apply { setImageDrawable(iconGetter(item)) }
-            view.requireViewById<TextView>(R.id.text).apply { setText(textGetter(item)) }
-
-            return view
-        }
-    }
-
-    private inner class UserAdapter : BaseUserSwitcherAdapter(userSwitcherController) {
-        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
-            val item = getItem(position)
-            var view = convertView as ViewGroup?
-            if (view == null) {
-                view =
-                    layoutInflater.inflate(R.layout.user_switcher_fullscreen_item, parent, false)
-                        as ViewGroup
-            }
-            (view.getChildAt(0) as ImageView).apply { setImageDrawable(getDrawable(item)) }
-            (view.getChildAt(1) as TextView).apply { setText(getName(getContext(), item)) }
-
-            view.setEnabled(item.isSwitchToEnabled)
-            UserSwitcherController.setSelectableAlpha(view)
-            view.setTag(USER_VIEW)
-            return view
-        }
-
-        override fun getName(context: Context, item: UserRecord, isTablet: Boolean): String {
-            return if (item == manageUserRecord) {
-                getString(R.string.manage_users)
-            } else {
-                super.getName(context, item, isTablet)
-            }
-        }
-
-        fun findUserIcon(item: UserRecord, isTablet: Boolean = false): Drawable {
-            if (item == manageUserRecord) {
-                return getDrawable(R.drawable.ic_manage_users)
-            }
-            if (item.info == null) {
-                return getIconDrawable(this@UserSwitcherActivity, item, isTablet)
-            }
-            val userIcon = userManager.getUserIcon(item.info.id)
-            if (userIcon != null) {
-                return BitmapDrawable(userIcon)
-            }
-            return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
-        }
-
-        fun getTotalUserViews(): Int {
-            return users.count { item -> !doNotRenderUserView(item) }
-        }
-
-        fun doNotRenderUserView(item: UserRecord): Boolean {
-            return item.isAddUser || item.isAddSupervisedUser || item.isGuest && item.info == null
-        }
-
-        private fun getDrawable(item: UserRecord): Drawable {
-            var drawable =
-                if (item.isGuest) {
-                    getDrawable(R.drawable.ic_account_circle)
-                } else {
-                    findUserIcon(item)
-                }
-            drawable.mutate()
-
-            if (!item.isCurrent && !item.isSwitchToEnabled) {
-                drawable.setTint(
-                    resources.getColor(
-                        R.color.kg_user_switcher_restricted_avatar_icon_color,
-                        getTheme()
-                    )
-                )
-            }
-
-            val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate() as LayerDrawable
-            if (item == userSwitcherController.currentUserRecord) {
-                (ld.findDrawableByLayerId(R.id.ring) as GradientDrawable).apply {
-                    val stroke =
-                        resources.getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
-                    val color =
-                        Utils.getColorAttrDefaultColor(
-                            this@UserSwitcherActivity,
-                            com.android.internal.R.attr.colorAccentPrimary
-                        )
-
-                    setStroke(stroke, color)
-                }
-            }
-
-            ld.setDrawableByLayerId(R.id.user_avatar, drawable)
-            return ld
-        }
-
-        override fun notifyDataSetChanged() {
-            super.notifyDataSetChanged()
-            buildUserViews()
-        }
-    }
-
-    companion object {
-        private const val TAG = "UserSwitcherActivity"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index 9370286..d4fb563 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -47,6 +47,9 @@
      * If not disabled, this is `null`.
      */
     @JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null,
+
+    /** Whether this record is to go to the Settings page to manage users. */
+    @JvmField val isManageUsers: Boolean = false
 ) {
     /** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
     fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
index 1b4746a..dc004f3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserActionsUtil.kt
@@ -82,6 +82,15 @@
         )
     }
 
+    fun canManageUsers(
+        repository: UserRepository,
+        isUserSwitcherEnabled: Boolean,
+        isAddUsersFromLockScreenEnabled: Boolean,
+    ): Boolean {
+        return isUserSwitcherEnabled &&
+            (repository.getSelectedUserInfo().isAdmin || isAddUsersFromLockScreenEnabled)
+    }
+
     /**
      * Returns `true` if the current user is allowed to add users to the device; `false` otherwise.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 142a328..ba5a82a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -102,6 +102,7 @@
     interface UserCallback {
         /** Returns `true` if this callback can be cleaned-up. */
         fun isEvictable(): Boolean = false
+
         /** Notifies that the state of users on the device has changed. */
         fun onUserStateChanged()
     }
@@ -164,10 +165,11 @@
         get() =
             if (isNewImpl) {
                 combine(
+                    repository.selectedUserInfo,
                     repository.userInfos,
                     repository.userSwitcherSettings,
                     keyguardInteractor.isKeyguardShowing,
-                ) { userInfos, settings, isDeviceLocked ->
+                ) { _, userInfos, settings, isDeviceLocked ->
                     buildList {
                         val hasGuestUser = userInfos.any { it.isGuest }
                         if (
@@ -183,35 +185,45 @@
                             add(UserActionModel.ENTER_GUEST_MODE)
                         }
 
-                        if (isDeviceLocked && !settings.isAddUsersFromLockscreen) {
+                        if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
                             // The device is locked and our setting to allow actions that add users
                             // from the lock-screen is not enabled. The guest action from above is
                             // always allowed, even when the device is locked, but the various "add
                             // user" actions below are not. We can finish building the list here.
-                            return@buildList
+
+                            val canCreateUsers =
+                                UserActionsUtil.canCreateUser(
+                                    manager,
+                                    repository,
+                                    settings.isUserSwitcherEnabled,
+                                    settings.isAddUsersFromLockscreen,
+                                )
+
+                            if (canCreateUsers) {
+                                add(UserActionModel.ADD_USER)
+                            }
+
+                            if (
+                                UserActionsUtil.canCreateSupervisedUser(
+                                    manager,
+                                    repository,
+                                    settings.isUserSwitcherEnabled,
+                                    settings.isAddUsersFromLockscreen,
+                                    supervisedUserPackageName,
+                                )
+                            ) {
+                                add(UserActionModel.ADD_SUPERVISED_USER)
+                            }
                         }
 
                         if (
-                            UserActionsUtil.canCreateUser(
-                                manager,
+                            UserActionsUtil.canManageUsers(
                                 repository,
                                 settings.isUserSwitcherEnabled,
                                 settings.isAddUsersFromLockscreen,
                             )
                         ) {
-                            add(UserActionModel.ADD_USER)
-                        }
-
-                        if (
-                            UserActionsUtil.canCreateSupervisedUser(
-                                manager,
-                                repository,
-                                settings.isUserSwitcherEnabled,
-                                settings.isAddUsersFromLockscreen,
-                                supervisedUserPackageName,
-                            )
-                        ) {
-                            add(UserActionModel.ADD_SUPERVISED_USER)
+                            add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
                         }
                     }
                 }
@@ -264,7 +276,10 @@
                                 toRecord(
                                     action = it,
                                     selectedUserId = selectedUserInfo.id,
-                                    isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
+                                    isRestricted =
+                                        it != UserActionModel.ENTER_GUEST_MODE &&
+                                            it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+                                            !settings.isAddUsersFromLockscreen,
                                 )
                             }
                     )
@@ -482,12 +497,12 @@
                             .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
                             .setPackage(supervisedUserPackageName)
                             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
-                        /* dismissShade= */ false,
+                        /* dismissShade= */ true,
                     )
                 UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
                     activityStarter.startActivity(
                         Intent(Settings.ACTION_USER_SETTINGS),
-                        /* dismissShade= */ false,
+                        /* dismissShade= */ true,
                     )
             }
         } else {
@@ -575,20 +590,13 @@
     private suspend fun toRecord(
         action: UserActionModel,
         selectedUserId: Int,
-        isAddFromLockscreenEnabled: Boolean,
+        isRestricted: Boolean,
     ): UserRecord {
         return LegacyUserDataHelper.createRecord(
             context = applicationContext,
             selectedUserId = selectedUserId,
             actionType = action,
-            isRestricted =
-                if (action == UserActionModel.ENTER_GUEST_MODE) {
-                    // Entering guest mode is never restricted, so it's allowed to happen from the
-                    // lockscreen even if the "add from lockscreen" system setting is off.
-                    false
-                } else {
-                    !isAddFromLockscreenEnabled
-                },
+            isRestricted = isRestricted,
             isSwitchToEnabled =
                 canSwitchUsers(selectedUserId) &&
                     // If the user is auto-created is must not be currently resetting.
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
index 137de15..03a7470 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -80,6 +80,7 @@
                     context = context,
                     selectedUserId = selectedUserId,
                 ),
+            isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
         )
     }
 
@@ -90,6 +91,7 @@
             record.isAddUser -> UserActionModel.ADD_USER
             record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
             record.isGuest -> UserActionModel.ENTER_GUEST_MODE
+            record.isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
             else -> error("Not a known action: $record")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index 15fdc35..e74232d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.StringRes
 import com.android.systemui.R
 import com.android.systemui.user.data.source.UserRecord
-import kotlin.math.ceil
 
 /**
  * Defines utility functions for helping with legacy UI code for users.
@@ -33,16 +32,6 @@
  */
 object LegacyUserUiHelper {
 
-    /** Returns the maximum number of columns for user items in the user switcher. */
-    fun getMaxUserSwitcherItemColumns(userCount: Int): Int {
-        // TODO(b/243844097): remove this once we remove the old user switcher implementation.
-        return if (userCount < 5) {
-            4
-        } else {
-            ceil(userCount / 2.0).toInt()
-        }
-    }
-
     @JvmStatic
     @DrawableRes
     fun getUserSwitcherActionIconResourceId(
@@ -50,6 +39,7 @@
         isGuest: Boolean,
         isAddSupervisedUser: Boolean,
         isTablet: Boolean = false,
+        isManageUsers: Boolean,
     ): Int {
         return if (isAddUser && isTablet) {
             R.drawable.ic_account_circle_filled
@@ -59,6 +49,8 @@
             R.drawable.ic_account_circle
         } else if (isAddSupervisedUser) {
             R.drawable.ic_add_supervised_user
+        } else if (isManageUsers) {
+            R.drawable.ic_manage_users
         } else {
             R.drawable.ic_avatar_user
         }
@@ -85,6 +77,7 @@
                         isAddUser = record.isAddUser,
                         isAddSupervisedUser = record.isAddSupervisedUser,
                         isTablet = isTablet,
+                        isManageUsers = record.isManageUsers,
                     )
                 )
         }
@@ -114,8 +107,9 @@
         isAddUser: Boolean,
         isAddSupervisedUser: Boolean,
         isTablet: Boolean = false,
+        isManageUsers: Boolean,
     ): Int {
-        check(isGuest || isAddUser || isAddSupervisedUser)
+        check(isGuest || isAddUser || isAddSupervisedUser || isManageUsers)
 
         return when {
             isGuest && isGuestUserAutoCreated && isGuestUserResetting ->
@@ -125,6 +119,7 @@
             isGuest -> com.android.internal.R.string.guest_name
             isAddUser -> com.android.settingslib.R.string.user_add_user
             isAddSupervisedUser -> R.string.add_user_supervised
+            isManageUsers -> R.string.manage_users
             else -> error("This should never happen!")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 5b83df7..219dae2 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -19,7 +19,6 @@
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.R
 import com.android.systemui.common.ui.drawable.CircularDrawable
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -30,6 +29,7 @@
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
 import javax.inject.Inject
+import kotlin.math.ceil
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -52,8 +52,7 @@
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
 
     /** The maximum number of columns that the user selection grid should use. */
-    val maximumUserColumns: Flow<Int> =
-        users.map { LegacyUserUiHelper.getMaxUserSwitcherItemColumns(it.size) }
+    val maximumUserColumns: Flow<Int> = users.map { getMaxUserSwitcherItemColumns(it.size) }
 
     private val _isMenuVisible = MutableStateFlow(false)
     /**
@@ -118,6 +117,15 @@
         _isMenuVisible.value = false
     }
 
+    /** Returns the maximum number of columns for user items in the user switcher. */
+    private fun getMaxUserSwitcherItemColumns(userCount: Int): Int {
+        return if (userCount < 5) {
+            4
+        } else {
+            ceil(userCount / 2.0).toInt()
+        }
+    }
+
     private fun createFinishRequestedFlow(): Flow<Boolean> {
         var mostRecentSelectedUserId: Int? = null
         var mostRecentIsInteractive: Boolean? = null
@@ -171,27 +179,23 @@
         return UserActionViewModel(
             viewKey = model.ordinal.toLong(),
             iconResourceId =
-                if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
-                    R.drawable.ic_manage_users
-                } else {
-                    LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
-                        isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
-                        isAddUser = model == UserActionModel.ADD_USER,
-                        isGuest = model == UserActionModel.ENTER_GUEST_MODE,
-                    )
-                },
+                LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
+                    isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
+                    isAddUser = model == UserActionModel.ADD_USER,
+                    isGuest = model == UserActionModel.ENTER_GUEST_MODE,
+                    isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    isTablet = true,
+                ),
             textResourceId =
-                if (model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) {
-                    R.string.manage_users
-                } else {
-                    LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
-                        isGuest = model == UserActionModel.ENTER_GUEST_MODE,
-                        isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
-                        isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
-                        isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
-                        isAddUser = model == UserActionModel.ADD_USER,
-                    )
-                },
+                LegacyUserUiHelper.getUserSwitcherActionTextResourceId(
+                    isGuest = model == UserActionModel.ENTER_GUEST_MODE,
+                    isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
+                    isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
+                    isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
+                    isAddUser = model == UserActionModel.ADD_USER,
+                    isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+                    isTablet = true,
+                ),
             onClicked = {
                 userInteractor.executeAction(action = model)
                 // We don't finish because we want to show a dialog over the full-screen UI and
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index c6ebaa8..48e8239 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -221,15 +221,17 @@
     public void onResourcesUpdate_callsThroughOnRotationChange() {
         // Rotation is the same, shouldn't cause an update
         mKeyguardSecurityContainerController.updateResources();
-        verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+        verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
 
         // Update rotation. Should trigger update
         mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
 
         mKeyguardSecurityContainerController.updateResources();
-        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
     }
 
     private void touchDown() {
@@ -263,8 +265,9 @@
                 .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
 
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
-        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
     }
 
     @Test
@@ -275,8 +278,9 @@
                 .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
 
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
-        verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+        verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
     }
 
     @Test
@@ -285,8 +289,26 @@
         setupGetSecurityView();
 
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
-        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+    }
+
+    @Test
+    public void addUserSwitcherCallback() {
+        ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
+                captor = ArgumentCaptor.forClass(
+                KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
+
+        setupGetSecurityView();
+
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
+        verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
+                any(UserSwitcherController.class),
+                captor.capture());
+        captor.getValue().showUnlockToContinueMessage();
+        verify(mKeyguardPasswordViewControllerMock).showMessage(
+                getContext().getString(R.string.keyguard_unlock_to_continue), null);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 52f8825..82d3ca7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -119,7 +119,7 @@
         int systemBarInsetAmount = 0;
 
         mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, () -> {});
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -141,7 +141,7 @@
         int systemBarInsetAmount = paddingBottom + 1;
 
         mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, () -> {});
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -158,9 +158,10 @@
     @Test
     public void testDefaultViewMode() {
         mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, () -> {
+                });
         mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, () -> {});
         ConstraintSet.Constraint viewFlipperConstraint =
                 getViewConstraint(mSecurityViewFlipper.getId());
         assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
@@ -377,7 +378,7 @@
     private void setupUserSwitcher() {
         when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
         mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
-                mGlobalSettings, mFalsingManager, mUserSwitcherController);
+                mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {});
     }
 
     private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -387,7 +388,8 @@
                     0 /* flags */);
             users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
                     false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
-                    false /* isAddSupervisedUser */, null /* enforcedAdmin */));
+                    false /* isAddSupervisedUser */, null /* enforcedAdmin */,
+                    false /* isManageUsers */));
         }
         return users;
     }
@@ -395,7 +397,7 @@
     private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
         int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
         mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, () -> {});
     }
 
     /** Get the ConstraintLayout constraint of the view. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index d52612b..f2ae7a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,6 +35,8 @@
 import android.view.WindowManager
 import android.widget.ScrollView
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.R
@@ -143,6 +145,35 @@
     }
 
     @Test
+    fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
+        val container = initializeFingerprintContainer(
+            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
+        )
+        waitForIdleSync()
+
+        val requestID = authContainer?.requestId ?: 0L
+
+        // Simulate keyboard was shown on the credential view
+        val windowInsetsController = container.windowInsetsController
+        spyOn(windowInsetsController)
+        spyOn(container.rootWindowInsets)
+        doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
+
+        container.onWindowFocusChanged(false)
+        waitForIdleSync()
+
+        // Expect hiding IME request will be invoked when dismissing the view
+        verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
+
+        verify(callback).onDismissed(
+            eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+            eq<ByteArray?>(null), /* credentialAttestation */
+            eq(requestID)
+        )
+        assertThat(container.parent).isNull()
+    }
+
+    @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
         container.mBiometricCallback.onAction(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 91214a8..e7e6918 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -38,6 +38,8 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.util.DeviceConfigProxyFake;
 
 import org.junit.Before;
@@ -47,6 +49,9 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import javax.inject.Provider;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -55,11 +60,15 @@
     @Mock
     private ClipboardManager mClipboardManager;
     @Mock
-    private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
+    private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
+    @Mock
+    private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
     @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
     private UiEventLogger mUiEventLogger;
+    @Mock
+    private FeatureFlags mFeatureFlags;
     private DeviceConfigProxyFake mDeviceConfigProxy;
 
     private ClipData mSampleClipData;
@@ -72,12 +81,17 @@
     @Captor
     private ArgumentCaptor<String> mStringCaptor;
 
+    @Spy
+    private Provider<ClipboardOverlayController> mOverlayControllerProvider;
+
 
     @Before
     public void setup() {
+        mOverlayControllerProvider = () -> mOverlayController;
+
         MockitoAnnotations.initMocks(this);
-        when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
-                mOverlayController);
+        when(mClipboardOverlayControllerLegacyFactory.create(any()))
+                .thenReturn(mOverlayControllerLegacy);
         when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
 
 
@@ -94,7 +108,8 @@
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "false", false);
         ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
         listener.start();
         verifyZeroInteractions(mClipboardManager);
         verifyZeroInteractions(mUiEventLogger);
@@ -105,7 +120,8 @@
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
         ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
         listener.start();
         verify(mClipboardManager).addPrimaryClipChangedListener(any());
         verifyZeroInteractions(mUiEventLogger);
@@ -113,16 +129,58 @@
 
     @Test
     public void test_consecutiveCopies() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
         ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
         listener.start();
         listener.onPrimaryClipChanged();
 
-        verify(mClipboardOverlayControllerFactory).create(any());
+        verify(mClipboardOverlayControllerLegacyFactory).create(any());
 
-        verify(mOverlayController).setClipData(mClipDataCaptor.capture(), mStringCaptor.capture());
+        verify(mOverlayControllerLegacy).setClipData(
+                mClipDataCaptor.capture(), mStringCaptor.capture());
+
+        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+        assertEquals(mSampleSource, mStringCaptor.getValue());
+
+        verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
+
+        // Should clear the overlay controller
+        mRunnableCaptor.getValue().run();
+
+        listener.onPrimaryClipChanged();
+
+        verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+
+        // Not calling the runnable here, just change the clip again and verify that the overlay is
+        // NOT recreated.
+
+        listener.onPrimaryClipChanged();
+
+        verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
+        verifyZeroInteractions(mOverlayControllerProvider);
+    }
+
+    @Test
+    public void test_consecutiveCopies_new() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
+                "true", false);
+        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
+        listener.start();
+        listener.onPrimaryClipChanged();
+
+        verify(mOverlayControllerProvider).get();
+
+        verify(mOverlayController).setClipData(
+                mClipDataCaptor.capture(), mStringCaptor.capture());
 
         assertEquals(mSampleClipData, mClipDataCaptor.getValue());
         assertEquals(mSampleSource, mStringCaptor.getValue());
@@ -134,14 +192,15 @@
 
         listener.onPrimaryClipChanged();
 
-        verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+        verify(mOverlayControllerProvider, times(2)).get();
 
         // Not calling the runnable here, just change the clip again and verify that the overlay is
         // NOT recreated.
 
         listener.onPrimaryClipChanged();
 
-        verify(mClipboardOverlayControllerFactory, times(2)).create(any());
+        verify(mOverlayControllerProvider, times(2)).get();
+        verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
     }
 
     @Test
@@ -169,4 +228,40 @@
         assertTrue(ClipboardListener.shouldSuppressOverlay(suppressableClipData,
                 ClipboardListener.SHELL_PACKAGE, false));
     }
+
+    @Test
+    public void test_logging_enterAndReenter() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
+
+        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
+        listener.start();
+
+        listener.onPrimaryClipChanged();
+        listener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+    }
+
+    @Test
+    public void test_logging_enterAndReenter_new() {
+        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
+
+        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardManager, mUiEventLogger, mFeatureFlags);
+        listener.start();
+
+        listener.onPrimaryClipChanged();
+        listener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
new file mode 100644
index 0000000..d96ca91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.clipboardoverlay;
+
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.PersistableBundle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.screenshot.TimeoutHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipboardOverlayControllerTest extends SysuiTestCase {
+
+    private ClipboardOverlayController mOverlayController;
+    @Mock
+    private ClipboardOverlayView mClipboardOverlayView;
+    @Mock
+    private ClipboardOverlayWindow mClipboardOverlayWindow;
+    @Mock
+    private BroadcastSender mBroadcastSender;
+    @Mock
+    private TimeoutHandler mTimeoutHandler;
+    @Mock
+    private UiEventLogger mUiEventLogger;
+
+    @Mock
+    private Animator mAnimator;
+
+    private ClipData mSampleClipData;
+
+    @Captor
+    private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
+    private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
+        when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+
+        mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
+                new ClipData.Item("Test Item"));
+
+        mOverlayController = new ClipboardOverlayController(
+                mContext,
+                mClipboardOverlayView,
+                mClipboardOverlayWindow,
+                getFakeBroadcastDispatcher(),
+                mBroadcastSender,
+                mTimeoutHandler,
+                mUiEventLogger);
+        verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
+        mCallbacks = mOverlayCallbacksCaptor.getValue();
+    }
+
+    @Test
+    public void test_setClipData_nullData() {
+        ClipData clipData = null;
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(0)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_invalidImageData() {
+        ClipData clipData = new ClipData("", new String[]{"image/png"},
+                new ClipData.Item(Uri.parse("")));
+
+        mOverlayController.setClipData(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(0)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_textData() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_sensitiveTextData() {
+        ClipDescription description = mSampleClipData.getDescription();
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+        description.setExtras(b);
+        ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+        mOverlayController.setClipData(data, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_repeatedCalls() {
+        when(mAnimator.isRunning()).thenReturn(true);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onShareTapped() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mCallbacks.onShareButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onDismissTapped() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_multipleDismissals_dismissesOnce() {
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+        verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
deleted file mode 100644
index c7c2cd8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEventTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.clipboardoverlay;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.DeviceConfigProxyFake;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ClipboardOverlayEventTest extends SysuiTestCase {
-
-    @Mock
-    private ClipboardManager mClipboardManager;
-    @Mock
-    private ClipboardOverlayControllerFactory mClipboardOverlayControllerFactory;
-    @Mock
-    private ClipboardOverlayController mOverlayController;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-
-    private final String mSampleSource = "Example source";
-
-    private ClipboardListener mClipboardListener;
-
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mClipboardOverlayControllerFactory.create(any())).thenReturn(
-                mOverlayController);
-        when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
-        ClipData sampleClipData = new ClipData("Test", new String[]{"text/plain"},
-                new ClipData.Item("Test Item"));
-        when(mClipboardManager.getPrimaryClip()).thenReturn(sampleClipData);
-        when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
-
-        DeviceConfigProxyFake deviceConfigProxy = new DeviceConfigProxyFake();
-        deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
-                "true", false);
-
-        mClipboardListener = new ClipboardListener(getContext(), deviceConfigProxy,
-                mClipboardOverlayControllerFactory, mClipboardManager, mUiEventLogger);
-    }
-
-    @Test
-    public void test_enterAndReenter() {
-        mClipboardListener.start();
-
-        mClipboardListener.onPrimaryClipChanged();
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mUiEventLogger, times(1)).log(
-                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
-        verify(mUiEventLogger, times(1)).log(
-                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b6d7559..b4d5464 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -12,20 +12,20 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.systemui.keyguard.domain.usecase
+package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Intent
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
@@ -195,6 +195,7 @@
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+    @Mock private lateinit var expandable: Expandable
 
     private lateinit var underTest: KeyguardQuickAffordanceInteractor
 
@@ -208,6 +209,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(expandable.activityLaunchController()).thenReturn(animationController)
 
         homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
         underTest =
@@ -259,7 +261,7 @@
 
         underTest.onQuickAffordanceClicked(
             configKey = homeControls::class,
-            animationController = animationController,
+            expandable = expandable,
         )
 
         if (startActivity) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 1dd919a..65fd6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -12,9 +12,10 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.systemui.keyguard.domain.usecase
+package com.android.systemui.keyguard.domain.interactor
 
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -22,13 +23,12 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -103,6 +103,7 @@
         homeControls.setState(
             KeyguardQuickAffordanceConfig.State.Visible(
                 icon = ICON,
+                toggle = KeyguardQuickAffordanceToggleState.On,
             )
         )
 
@@ -123,6 +124,7 @@
         assertThat(visibleModel.icon).isEqualTo(ICON)
         assertThat(visibleModel.icon.contentDescription)
             .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+        assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On)
         job.cancel()
     }
 
@@ -152,6 +154,7 @@
         assertThat(visibleModel.icon).isEqualTo(ICON)
         assertThat(visibleModel.icon.contentDescription)
             .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+        assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported)
         job.cancel()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index 6ea1daa..e99c139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -17,7 +17,7 @@
 
 package com.android.systemui.keyguard.domain.quickaffordance
 
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,7 +40,7 @@
     override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
 
     override fun onQuickAffordanceClicked(
-        animationController: ActivityLaunchAnimator.Controller?,
+        expandable: Expandable?,
     ): OnClickedResult {
         return onClickedResult
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index dede4ec..a809f05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -20,7 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
@@ -44,7 +44,7 @@
 class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var component: ControlsComponent
-    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+    @Mock private lateinit var expandable: Expandable
 
     private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
 
@@ -103,7 +103,7 @@
     fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
         whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
 
-        val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+        val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
 
         assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
         assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
@@ -113,7 +113,7 @@
     fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
         whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
 
-        val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+        val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
 
         assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
         assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 0a4478f..98dc4c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -24,11 +24,13 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
@@ -40,7 +42,6 @@
 import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -135,8 +136,11 @@
     @Test
     fun onQuickAffordanceClicked() {
         val animationController: ActivityLaunchAnimator.Controller = mock()
+        val expandable: Expandable = mock {
+            whenever(this.activityLaunchController()).thenReturn(animationController)
+        }
 
-        assertThat(underTest.onQuickAffordanceClicked(animationController))
+        assertThat(underTest.onQuickAffordanceClicked(expandable))
             .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
         verify(walletController)
             .startQuickAccessUiIntent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 96544e7..d674c89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -20,7 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -59,7 +60,7 @@
 @RunWith(JUnit4::class)
 class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
 
-    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+    @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -130,6 +131,7 @@
             TestConfig(
                 isVisible = true,
                 isClickable = true,
+                isActivated = true,
                 icon = mock(),
                 canShowWhileLocked = false,
                 intent = Intent("action"),
@@ -505,6 +507,12 @@
                 }
                 KeyguardQuickAffordanceConfig.State.Visible(
                     icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+                    toggle =
+                        when (testConfig.isActivated) {
+                            true -> KeyguardQuickAffordanceToggleState.On
+                            false -> KeyguardQuickAffordanceToggleState.Off
+                            null -> KeyguardQuickAffordanceToggleState.NotSupported
+                        }
                 )
             } else {
                 KeyguardQuickAffordanceConfig.State.Hidden
@@ -521,12 +529,13 @@
         checkNotNull(viewModel)
         assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
         assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
+        assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
         if (testConfig.isVisible) {
             assertThat(viewModel.icon).isEqualTo(testConfig.icon)
             viewModel.onClicked.invoke(
                 KeyguardQuickAffordanceViewModel.OnClickedParameters(
                     configKey = configKey,
-                    animationController = animationController,
+                    expandable = expandable,
                 )
             )
             if (testConfig.intent != null) {
@@ -542,6 +551,7 @@
     private data class TestConfig(
         val isVisible: Boolean,
         val isClickable: Boolean = false,
+        val isActivated: Boolean = false,
         val icon: Icon? = null,
         val canShowWhileLocked: Boolean = false,
         val intent: Intent? = null,
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 0e9d279..6adce7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -80,6 +80,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
 import com.android.systemui.navigationbar.buttons.DeadZone;
@@ -197,6 +198,8 @@
     @Mock
     private UserContextProvider mUserContextProvider;
     @Mock
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
     private Resources mResources;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -474,7 +477,8 @@
                 mNavigationBarTransitions,
                 mEdgeBackGestureHandler,
                 Optional.of(mock(BackAnimation.class)),
-                mUserContextProvider));
+                mUserContextProvider,
+                mWakefulnessLifecycle));
     }
 
     private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 4e9b232..c377c37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.power.PowerUI.WarningsUI;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -84,6 +85,7 @@
     private PowerUI mPowerUI;
     @Mock private EnhancedEstimates mEnhancedEstimates;
     @Mock private PowerManager mPowerManager;
+    @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock private IThermalService mThermalServiceMock;
     private IThermalEventListener mUsbThermalEventListener;
     private IThermalEventListener mSkinThermalEventListener;
@@ -680,7 +682,7 @@
     private void createPowerUi() {
         mPowerUI = new PowerUI(
                 mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
-                mMockWarnings, mEnhancedEstimates, mPowerManager);
+                mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager);
         mPowerUI.mThermalService = mThermalServiceMock;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 2a4996f..760bb9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -192,16 +192,6 @@
         // UserManager change.
         assertThat(iconTint()).isNull()
 
-        // Trigger a user info change: there should now be a tint.
-        userInfoController.updateInfo { userAccount = "doe" }
-        assertThat(iconTint())
-            .isEqualTo(
-                Utils.getColorAttrDefaultColor(
-                    context,
-                    android.R.attr.colorForeground,
-                )
-            )
-
         // Make sure we don't tint the icon if it is a user image (and not the default image), even
         // in guest mode.
         userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
new file mode 100644
index 0000000..c6ce51a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DraggableConstraintLayoutTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class DraggableConstraintLayoutTest extends SysuiTestCase {
+
+    @Mock
+    DraggableConstraintLayout.SwipeDismissCallbacks mCallbacks;
+
+    private DraggableConstraintLayout mDraggableConstraintLayout;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mDraggableConstraintLayout = new DraggableConstraintLayout(mContext, null, 0);
+    }
+
+    @Test
+    public void test_dismissDoesNotCallSwipeInitiated() {
+        mDraggableConstraintLayout.setCallbacks(mCallbacks);
+
+        mDraggableConstraintLayout.dismiss();
+
+        verify(mCallbacks, never()).onSwipeDismissInitiated(any());
+    }
+
+    @Test
+    public void test_onTouchCallsOnInteraction() {
+        mDraggableConstraintLayout.setCallbacks(mCallbacks);
+
+        mDraggableConstraintLayout.onInterceptTouchEvent(
+                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+
+        verify(mCallbacks).onInteraction();
+    }
+
+    @Test
+    public void test_callbacksNotSet() {
+        // just test that it doesn't throw an NPE
+        mDraggableConstraintLayout.onInterceptTouchEvent(
+                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+        mDraggableConstraintLayout.onInterceptHoverEvent(
+                MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0));
+        mDraggableConstraintLayout.dismiss();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index 3f641df..ca65987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -91,6 +91,8 @@
         verifyNoMoreInteractions(mLogger);
         clearInvocations(mLogger);
 
+        when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams());
+
         mViewBinder.unbindHeadsUpView(mEntry);
         verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
         verifyNoMoreInteractions(mLogger);
@@ -139,6 +141,8 @@
         verifyNoMoreInteractions(mLogger);
         clearInvocations(mLogger);
 
+        when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(new RowContentBindParams());
+
         mViewBinder.unbindHeadsUpView(mEntry);
         verify(mLogger).currentOngoingBindingAborted(eq(mEntry));
         verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
@@ -150,4 +154,30 @@
         verifyNoMoreInteractions(mLogger);
         clearInvocations(mLogger);
     }
+
+    @Test
+    public void testLoggingForLateUnbindFlow() {
+        AtomicReference<NotifBindPipeline.BindCallback> callback = new AtomicReference<>();
+        when(mBindStage.requestRebind(any(), any())).then(i -> {
+            callback.set(i.getArgument(1));
+            return new CancellationSignal();
+        });
+
+        mViewBinder.bindHeadsUpView(mEntry, null);
+        verify(mLogger).startBindingHun(eq(mEntry));
+        verifyNoMoreInteractions(mLogger);
+        clearInvocations(mLogger);
+
+        callback.get().onBindFinished(mEntry);
+        verify(mLogger).entryBoundSuccessfully(eq(mEntry));
+        verifyNoMoreInteractions(mLogger);
+        clearInvocations(mLogger);
+
+        when(mBindStage.tryGetStageParams(eq(mEntry))).thenReturn(null);
+
+        mViewBinder.unbindHeadsUpView(mEntry);
+        verify(mLogger).entryBindStageParamsNullOnUnbind(eq(mEntry));
+        verifyNoMoreInteractions(mLogger);
+        clearInvocations(mLogger);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index ad3bd71..7c99568 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -21,6 +21,10 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -31,6 +35,7 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Log;
 
 import androidx.test.filters.SmallTest;
 
@@ -100,6 +105,67 @@
         verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags));
     }
 
+    class CountingWtfHandler implements Log.TerribleFailureHandler {
+        private Log.TerribleFailureHandler mOldHandler = null;
+        private int mWtfCount = 0;
+
+        public void register() {
+            mOldHandler = Log.setWtfHandler(this);
+        }
+
+        public void unregister() {
+            Log.setWtfHandler(mOldHandler);
+            mOldHandler = null;
+        }
+
+        @Override
+        public void onTerribleFailure(String tag, Log.TerribleFailure what, boolean system) {
+            mWtfCount++;
+        }
+
+        public int getWtfCount() {
+            return mWtfCount;
+        }
+    }
+
+    @Test
+    public void testGetStageParamsAfterCleanUp() {
+        // GIVEN an entry whose params have already been deleted.
+        RowContentBindParams originalParams = mRowContentBindStage.getStageParams(mEntry);
+        mRowContentBindStage.deleteStageParams(mEntry);
+
+        // WHEN a caller calls getStageParams.
+        CountingWtfHandler countingWtfHandler = new CountingWtfHandler();
+        countingWtfHandler.register();
+
+        RowContentBindParams blankParams = mRowContentBindStage.getStageParams(mEntry);
+
+        countingWtfHandler.unregister();
+
+        // THEN getStageParams logs a WTF and returns blank params created to avoid a crash.
+        assertEquals(1, countingWtfHandler.getWtfCount());
+        assertNotNull(blankParams);
+        assertNotSame(originalParams, blankParams);
+    }
+
+    @Test
+    public void testTryGetStageParamsAfterCleanUp() {
+        // GIVEN an entry whose params have already been deleted.
+        mRowContentBindStage.deleteStageParams(mEntry);
+
+        // WHEN a caller calls getStageParams.
+        CountingWtfHandler countingWtfHandler = new CountingWtfHandler();
+        countingWtfHandler.register();
+
+        RowContentBindParams nullParams = mRowContentBindStage.tryGetStageParams(mEntry);
+
+        countingWtfHandler.unregister();
+
+        // THEN getStageParams does NOT log a WTF and returns null to indicate missing params.
+        assertEquals(0, countingWtfHandler.getWtfCount());
+        assertNull(nullParams);
+    }
+
     @Test
     public void testRebindAllContentViews() {
         // GIVEN a view with content bound.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
index 76ecc1c..169f4fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
@@ -57,14 +57,18 @@
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.telephony.TelephonyListenerManager
 import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
+import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -123,7 +127,7 @@
     private val ownerId = UserHandle.USER_SYSTEM
     private val ownerInfo = UserInfo(ownerId, "Owner", null,
             UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
-                    UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM,
+                    UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
             UserManager.USER_TYPE_FULL_SYSTEM)
     private val guestId = 1234
     private val guestInfo = UserInfo(guestId, "Guest", null,
@@ -597,6 +601,76 @@
     }
 
     @Test
+    fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
+        `when`(
+            globalSettings.getIntForUser(
+                eq(Settings.Global.USER_SWITCHER_ENABLED),
+                anyInt(),
+                eq(UserHandle.USER_SYSTEM)
+            )
+        ).thenReturn(1)
+
+        `when`(
+            globalSettings.getIntForUser(
+                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
+                anyInt(),
+                eq(UserHandle.USER_SYSTEM)
+            )
+        ).thenReturn(1)
+        setupController()
+        assertTrue(userSwitcherController.canManageUsers())
+    }
+
+    @Test
+    fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
+        `when`(
+            globalSettings.getIntForUser(
+                eq(Settings.Global.USER_SWITCHER_ENABLED),
+                anyInt(),
+                eq(UserHandle.USER_SYSTEM)
+            )
+        ).thenReturn(0)
+
+        `when`(
+            globalSettings.getIntForUser(
+                eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
+                anyInt(),
+                eq(UserHandle.USER_SYSTEM)
+            )
+        ).thenReturn(1)
+        setupController()
+        assertFalse(userSwitcherController.canManageUsers())
+    }
+
+    @Test
+    fun testCanManageUser_userSwitcherEnabled_isAdmin() {
+        `when`(
+            globalSettings.getIntForUser(
+                eq(Settings.Global.USER_SWITCHER_ENABLED),
+                anyInt(),
+                eq(UserHandle.USER_SYSTEM)
+            )
+        ).thenReturn(1)
+
+        setupController()
+        assertTrue(userSwitcherController.canManageUsers())
+    }
+
+    @Test
+    fun testCanManageUser_userSwitcherDisabled_isAdmin() {
+        `when`(
+            globalSettings.getIntForUser(
+                eq(Settings.Global.USER_SWITCHER_ENABLED),
+                anyInt(),
+                eq(UserHandle.USER_SYSTEM)
+            )
+        ).thenReturn(0)
+
+        setupController()
+        assertFalse(userSwitcherController.canManageUsers())
+    }
+
+    @Test
     fun addUserSwitchCallback() {
         val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
         verify(broadcastDispatcher).registerReceiver(
@@ -632,4 +706,22 @@
         bgExecutor.runAllReady()
         verify(userManager).createGuest(context)
     }
+
+    @Test
+    fun onUserItemClicked_manageUsers() {
+        val manageUserRecord = LegacyUserDataHelper.createRecord(
+            mContext,
+            ownerId,
+            UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+            isRestricted = false,
+            isSwitchToEnabled = true
+        )
+
+        userSwitcherController.onUserListItemClicked(manageUserRecord, null)
+        val intentCaptor = kotlinArgumentCaptor<Intent>()
+        verify(activityStarter).startActivity(intentCaptor.capture(),
+            eq(true)
+        )
+        Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
deleted file mode 100644
index 3968bb7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user
-
-import android.app.Application
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.view.Window
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class UserSwitcherActivityTest : SysuiTestCase() {
-    @Mock
-    private lateinit var activity: UserSwitcherActivity
-    @Mock
-    private lateinit var userSwitcherController: UserSwitcherController
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var layoutInflater: LayoutInflater
-    @Mock
-    private lateinit var falsingCollector: FalsingCollector
-    @Mock
-    private lateinit var falsingManager: FalsingManager
-    @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var flags: FeatureFlags
-    @Mock
-    private lateinit var viewModelFactoryLazy: dagger.Lazy<UserSwitcherViewModel.Factory>
-    @Mock
-    private lateinit var onBackDispatcher: OnBackInvokedDispatcher
-    @Mock
-    private lateinit var decorView: View
-    @Mock
-    private lateinit var window: Window
-    @Mock
-    private lateinit var userSwitcherRootView: UserSwitcherRootView
-    @Captor
-    private lateinit var onBackInvokedCallback: ArgumentCaptor<OnBackInvokedCallback>
-    var isFinished = false
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        activity = spy(object : UserSwitcherActivity(
-            userSwitcherController,
-            broadcastDispatcher,
-            falsingCollector,
-            falsingManager,
-            userManager,
-            userTracker,
-            flags,
-            viewModelFactoryLazy,
-        ) {
-            override fun getOnBackInvokedDispatcher() = onBackDispatcher
-            override fun getMainExecutor(): Executor = FakeExecutor(FakeSystemClock())
-            override fun finish() {
-                isFinished = true
-            }
-        })
-        `when`(activity.window).thenReturn(window)
-        `when`(window.decorView).thenReturn(decorView)
-        `when`(activity.findViewById<UserSwitcherRootView>(R.id.user_switcher_root))
-                .thenReturn(userSwitcherRootView)
-        `when`(activity.findViewById<View>(R.id.cancel)).thenReturn(mock(View::class.java))
-        `when`(activity.findViewById<View>(R.id.add)).thenReturn(mock(View::class.java))
-        `when`(activity.application).thenReturn(mock(Application::class.java))
-        doNothing().`when`(activity).setContentView(anyInt())
-    }
-
-    @Test
-    fun testMaxColumns() {
-        assertThat(activity.getMaxColumns(3)).isEqualTo(4)
-        assertThat(activity.getMaxColumns(4)).isEqualTo(4)
-        assertThat(activity.getMaxColumns(5)).isEqualTo(3)
-        assertThat(activity.getMaxColumns(6)).isEqualTo(3)
-        assertThat(activity.getMaxColumns(7)).isEqualTo(4)
-        assertThat(activity.getMaxColumns(9)).isEqualTo(5)
-    }
-
-    @Test
-    fun onCreate_callbackRegistration() {
-        activity.createActivity()
-        verify(onBackDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
-
-        activity.destroyActivity()
-        verify(onBackDispatcher).unregisterOnBackInvokedCallback(any())
-    }
-
-    @Test
-    fun onBackInvokedCallback_finishesActivity() {
-        activity.createActivity()
-        verify(onBackDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), onBackInvokedCallback.capture())
-
-        onBackInvokedCallback.value.onBackInvoked()
-        assertThat(isFinished).isTrue()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 37c378c..1540f85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -202,6 +202,7 @@
     fun `actions - device unlocked`() =
         runBlocking(IMMEDIATE) {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
+
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -215,6 +216,7 @@
                         UserActionModel.ENTER_GUEST_MODE,
                         UserActionModel.ADD_USER,
                         UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
                     )
                 )
 
@@ -276,6 +278,7 @@
                         UserActionModel.ENTER_GUEST_MODE,
                         UserActionModel.ADD_USER,
                         UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
                     )
                 )
 
@@ -283,7 +286,7 @@
         }
 
     @Test
-    fun `actions - device locked - only guest action is shown`() =
+    fun `actions - device locked - only guest action and manage user is shown`() =
         runBlocking(IMMEDIATE) {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -293,7 +296,13 @@
             var value: List<UserActionModel>? = null
             val job = underTest.actions.onEach { value = it }.launchIn(this)
 
-            assertThat(value).isEqualTo(listOf(UserActionModel.ENTER_GUEST_MODE))
+            assertThat(value)
+                .isEqualTo(
+                    listOf(
+                        UserActionModel.ENTER_GUEST_MODE,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+                    )
+                )
 
             job.cancel()
         }
@@ -330,7 +339,7 @@
             underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
 
             val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
             assertThat(intentCaptor.value.action)
                 .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
             assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
@@ -342,7 +351,7 @@
             underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
 
             val intentCaptor = kotlinArgumentCaptor<Intent>()
-            verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+            verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
             assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
         }
 
@@ -561,6 +570,7 @@
                         UserActionModel.ENTER_GUEST_MODE,
                         UserActionModel.ADD_USER,
                         UserActionModel.ADD_SUPERVISED_USER,
+                        UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
                     ),
             )
         }
@@ -705,7 +715,7 @@
             name,
             /* iconPath= */ "",
             /* flags= */ if (isPrimary) {
-                UserInfo.FLAG_PRIMARY
+                UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
             } else {
                 0
             },
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index aec5f5e..4cf63b3 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -3732,21 +3732,34 @@
             Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName
                     + " died: cancel current operations");
 
-            // handleCancel() causes the PerformFullTransportBackupTask to go on to
-            // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
-            // that the package being backed up doesn't get stuck in restricted mode until the
-            // backup time-out elapses.
-            for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
-                if (MORE_DEBUG) {
-                    Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
-                            + Integer.toHexString(token));
+            // Offload operation cancellation off the main thread as the cancellation callbacks
+            // might call out to BackupTransport. Other operations started on the same package
+            // before the cancellation callback has executed will also be cancelled by the callback.
+            Runnable cancellationRunnable = () -> {
+                // handleCancel() causes the PerformFullTransportBackupTask to go on to
+                // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
+                // that the package being backed up doesn't get stuck in restricted mode until the
+                // backup time-out elapses.
+                for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
+                    if (MORE_DEBUG) {
+                        Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
+                                + Integer.toHexString(token));
+                    }
+                    handleCancel(token, true /* cancelAll */);
                 }
-                handleCancel(token, true /* cancelAll */);
-            }
+            };
+            getThreadForAsyncOperation(/* operationName */ "agent-disconnected",
+                    cancellationRunnable).start();
+
             mAgentConnectLock.notifyAll();
         }
     }
 
+    @VisibleForTesting
+    Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
+        return new Thread(operation, operationName);
+    }
+
     /**
      * An application being installed will need a restore pass, then the {@link PackageManager} will
      * need to be told when the restore is finished.
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index fb8c5b1..41b62e6 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -363,7 +363,9 @@
         @Override
         public void handleMessage(@NonNull Message msg) {
             final int associationId = msg.what;
-            onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+            if (mSimulated.contains(associationId)) {
+                onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+            }
         }
     }
 }
diff --git a/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
similarity index 99%
rename from core/java/com/android/server/SystemConfig.java
rename to services/core/java/com/android/server/SystemConfig.java
index 2c9ef4f..b7f8d38 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -72,6 +72,8 @@
  * Loads global system configuration info.
  * Note: Initializing this class hits the disk and is slow.  This class should generally only be
  * accessed by the system_server process.
+ *
+ * @hide
  */
 public class SystemConfig {
     static final String TAG = "SystemConfig";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 593e21a5..1a4da7d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3937,7 +3937,8 @@
                     // Clear its scheduled jobs
                     JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
                     // Clearing data is a user-initiated action.
-                    js.cancelJobsForUid(appInfo.uid, JobParameters.STOP_REASON_USER,
+                    js.cancelJobsForUid(appInfo.uid, /* includeProxiedJobs */ true,
+                            JobParameters.STOP_REASON_USER,
                             JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED, "clear data");
 
                     // Clear its pending alarms
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 23c020e..3c1bf0b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1138,10 +1138,7 @@
         mHandler.removeCallbacksAndMessages(null);
 
         // Release any outstanding wakelocks we're still holding because of pending messages.
-        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
-        mWakelockController.releaseStateChangedSuspendBlocker();
-        mWakelockController.releaseProxPositiveSuspendBlocker();
-        mWakelockController.releaseProxNegativeSuspendBlocker();
+        mWakelockController.releaseAll();
 
         final float brightness = mPowerState != null
                 ? mPowerState.getScreenBrightness()
@@ -1729,7 +1726,7 @@
 
         // Grab a wake lock if we have unfinished business.
         if (!finished) {
-            mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
+            mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
         }
 
         // Notify the power manager when ready.
@@ -1749,7 +1746,7 @@
 
         // Release the wake lock when we have no unfinished business.
         if (finished) {
-            mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+            mWakelockController.releaseWakelock(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
         }
 
         // Record if dozing for future comparison.
@@ -2248,7 +2245,8 @@
                 mSensorManager.unregisterListener(mProximitySensorListener);
                 // release wake lock(must be last)
                 boolean proxDebounceSuspendBlockerReleased =
-                        mWakelockController.releaseProxDebounceSuspendBlocker();
+                        mWakelockController.releaseWakelock(
+                                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
                 if (proxDebounceSuspendBlockerReleased) {
                     mPendingProximityDebounceTime = -1;
                 }
@@ -2272,11 +2270,13 @@
             if (positive) {
                 mPendingProximity = PROXIMITY_POSITIVE;
                 mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
-                mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
+                mWakelockController.acquireWakelock(
+                        WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
             } else {
                 mPendingProximity = PROXIMITY_NEGATIVE;
                 mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
-                mWakelockController.acquireProxDebounceSuspendBlocker(); // acquire wake lock
+                mWakelockController.acquireWakelock(
+                        WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
             }
 
             // Debounce the new sensor reading.
@@ -2300,7 +2300,8 @@
                 updatePowerState();
                 // (must be last)
                 boolean proxDebounceSuspendBlockerReleased =
-                        mWakelockController.releaseProxDebounceSuspendBlocker();
+                        mWakelockController.releaseWakelock(
+                                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
                 if (proxDebounceSuspendBlockerReleased) {
                     mPendingProximityDebounceTime = -1;
                 }
@@ -2315,7 +2316,8 @@
     }
 
     private void sendOnStateChangedWithWakelock() {
-        boolean wakeLockAcquired = mWakelockController.acquireStateChangedSuspendBlocker();
+        boolean wakeLockAcquired = mWakelockController.acquireWakelock(
+                WakelockController.WAKE_LOCK_STATE_CHANGED);
         if (wakeLockAcquired) {
             mHandler.post(mWakelockController.getOnStateChangedRunnable());
         }
@@ -2479,13 +2481,13 @@
     }
 
     private void sendOnProximityPositiveWithWakelock() {
-        mWakelockController.acquireProxPositiveSuspendBlocker();
+        mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
         mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
     }
 
 
     private void sendOnProximityNegativeWithWakelock() {
-        mWakelockController.acquireProxNegativeSuspendBlocker();
+        mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
         mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
     }
 
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index cbf559f..6511f4f 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -16,12 +16,15 @@
 
 package com.android.server.display;
 
+import android.annotation.IntDef;
 import android.hardware.display.DisplayManagerInternal;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * A utility class to acquire/release suspend blockers and manage appropriate states around it.
@@ -29,8 +32,26 @@
  * display states as needed.
  */
 public final class WakelockController {
+    public static final int WAKE_LOCK_PROXIMITY_POSITIVE = 1;
+    public static final int WAKE_LOCK_PROXIMITY_NEGATIVE = 2;
+    public static final int WAKE_LOCK_PROXIMITY_DEBOUNCE = 3;
+    public static final int WAKE_LOCK_STATE_CHANGED = 4;
+    public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 5;
+
+    private static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS;
     private static final boolean DEBUG = false;
 
+    @IntDef(flag = true, prefix = "WAKE_LOCK_", value = {
+            WAKE_LOCK_PROXIMITY_POSITIVE,
+            WAKE_LOCK_PROXIMITY_NEGATIVE,
+            WAKE_LOCK_PROXIMITY_DEBOUNCE,
+            WAKE_LOCK_STATE_CHANGED,
+            WAKE_LOCK_UNFINISHED_BUSINESS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WAKE_LOCK_TYPE {
+    }
+
     // Asynchronous callbacks into the power manager service.
     // Only invoked from the handler thread while no locks are held.
     private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
@@ -58,17 +79,17 @@
     // (i.e. DisplayPowerController2).
     private boolean mOnStateChangedPending;
 
-    // Count of positive proximity messages currently held. Used to keep track of how many
-    // suspend blocker acquisitions are pending when shutting down the DisplayPowerController2.
-    // Should only be accessed on the Handler thread of the class managing the Display states
-    // (i.e. DisplayPowerController2).
-    private int mOnProximityPositiveMessages;
+    // When true, it implies that a positive proximity wakelock is currently held. Used to keep
+    // track if suspend blocker acquisitions is pending when shutting down the
+    // DisplayPowerController2. Should only be accessed on the Handler thread of the class
+    // managing the Display states (i.e. DisplayPowerController2).
+    private boolean mIsProximityPositiveAcquired;
 
-    // Count of negative proximity messages currently held. Used to keep track of how many
-    // suspend blocker acquisitions are pending when shutting down the DisplayPowerController2.
-    // Should only be accessed on the Handler thread of the class managing the Display states
-    // (i.e. DisplayPowerController2).
-    private int mOnProximityNegativeMessages;
+    // When true, it implies that a negative proximity wakelock is currently held. Used to keep
+    // track if suspend blocker acquisitions is pending when shutting down the
+    // DisplayPowerController2. Should only be accessed on the Handler thread of the class
+    // managing the Display states (i.e. DisplayPowerController2).
+    private boolean mIsProximityNegativeAcquired;
 
     /**
      * The constructor of WakelockController. Manages the initialization of all the local entities
@@ -87,9 +108,86 @@
     }
 
     /**
+     * A utility to acquire a wakelock
+     *
+     * @param wakelock The type of Wakelock to be acquired
+     * @return True of the wakelock is successfully acquired. False if it is already acquired
+     */
+    public boolean acquireWakelock(@WAKE_LOCK_TYPE int wakelock) {
+        return acquireWakelockInternal(wakelock);
+    }
+
+    /**
+     * A utility to release a wakelock
+     *
+     * @param wakelock The type of Wakelock to be released
+     * @return True of an acquired wakelock is successfully released. False if it is already
+     * acquired
+     */
+    public boolean releaseWakelock(@WAKE_LOCK_TYPE int wakelock) {
+        return releaseWakelockInternal(wakelock);
+    }
+
+    /**
+     * A utility to release all the wakelock acquired by the system
+     */
+    public void releaseAll() {
+        for (int i = WAKE_LOCK_PROXIMITY_POSITIVE; i < WAKE_LOCK_MAX; i++) {
+            releaseWakelockInternal(i);
+        }
+    }
+
+    private boolean acquireWakelockInternal(@WAKE_LOCK_TYPE int wakelock) {
+        switch (wakelock) {
+            case WAKE_LOCK_PROXIMITY_POSITIVE:
+                return acquireProxPositiveSuspendBlocker();
+            case WAKE_LOCK_PROXIMITY_NEGATIVE:
+                return acquireProxNegativeSuspendBlocker();
+            case WAKE_LOCK_PROXIMITY_DEBOUNCE:
+                return acquireProxDebounceSuspendBlocker();
+            case WAKE_LOCK_STATE_CHANGED:
+                return acquireStateChangedSuspendBlocker();
+            case WAKE_LOCK_UNFINISHED_BUSINESS:
+                return acquireUnfinishedBusinessSuspendBlocker();
+            default:
+                throw new RuntimeException("Invalid wakelock attempted to be acquired");
+        }
+    }
+
+    private boolean releaseWakelockInternal(@WAKE_LOCK_TYPE int wakelock) {
+        switch (wakelock) {
+            case WAKE_LOCK_PROXIMITY_POSITIVE:
+                return releaseProxPositiveSuspendBlocker();
+            case WAKE_LOCK_PROXIMITY_NEGATIVE:
+                return releaseProxNegativeSuspendBlocker();
+            case WAKE_LOCK_PROXIMITY_DEBOUNCE:
+                return releaseProxDebounceSuspendBlocker();
+            case WAKE_LOCK_STATE_CHANGED:
+                return releaseStateChangedSuspendBlocker();
+            case WAKE_LOCK_UNFINISHED_BUSINESS:
+                return releaseUnfinishedBusinessSuspendBlocker();
+            default:
+                throw new RuntimeException("Invalid wakelock attempted to be released");
+        }
+    }
+
+    /**
+     * Acquires the proximity positive wakelock and notifies the PowerManagerService about the
+     * changes.
+     */
+    private boolean acquireProxPositiveSuspendBlocker() {
+        if (!mIsProximityPositiveAcquired) {
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
+            mIsProximityPositiveAcquired = true;
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Acquires the state change wakelock and notifies the PowerManagerService about the changes.
      */
-    public boolean acquireStateChangedSuspendBlocker() {
+    private boolean acquireStateChangedSuspendBlocker() {
         // Grab a wake lock if we have change of the display state
         if (!mOnStateChangedPending) {
             if (DEBUG) {
@@ -105,18 +203,20 @@
     /**
      * Releases the state change wakelock and notifies the PowerManagerService about the changes.
      */
-    public void releaseStateChangedSuspendBlocker() {
+    private boolean releaseStateChangedSuspendBlocker() {
         if (mOnStateChangedPending) {
             mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
             mOnStateChangedPending = false;
+            return true;
         }
+        return false;
     }
 
     /**
      * Acquires the unfinished business wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public void acquireUnfinishedBusinessSuspendBlocker() {
+    private boolean acquireUnfinishedBusinessSuspendBlocker() {
         // Grab a wake lock if we have unfinished business.
         if (!mUnfinishedBusiness) {
             if (DEBUG) {
@@ -124,79 +224,84 @@
             }
             mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
             mUnfinishedBusiness = true;
+            return true;
         }
+        return false;
     }
 
     /**
      * Releases the unfinished business wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public void releaseUnfinishedBusinessSuspendBlocker() {
+    private boolean releaseUnfinishedBusinessSuspendBlocker() {
         if (mUnfinishedBusiness) {
             if (DEBUG) {
                 Slog.d(mTag, "Finished business...");
             }
             mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
             mUnfinishedBusiness = false;
+            return true;
         }
-    }
-
-    /**
-     * Acquires the proximity positive wakelock and notifies the PowerManagerService about the
-     * changes.
-     */
-    public void acquireProxPositiveSuspendBlocker() {
-        mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
-        mOnProximityPositiveMessages++;
+        return false;
     }
 
     /**
      * Releases the proximity positive wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public void releaseProxPositiveSuspendBlocker() {
-        for (int i = 0; i < mOnProximityPositiveMessages; i++) {
+    private boolean releaseProxPositiveSuspendBlocker() {
+        if (mIsProximityPositiveAcquired) {
             mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+            mIsProximityPositiveAcquired = false;
+            return true;
         }
-        mOnProximityPositiveMessages = 0;
+        return false;
     }
 
     /**
      * Acquires the proximity negative wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public void acquireProxNegativeSuspendBlocker() {
-        mOnProximityNegativeMessages++;
-        mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+    private boolean acquireProxNegativeSuspendBlocker() {
+        if (!mIsProximityNegativeAcquired) {
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+            mIsProximityNegativeAcquired = true;
+            return true;
+        }
+        return false;
     }
 
     /**
      * Releases the proximity negative wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public void releaseProxNegativeSuspendBlocker() {
-        for (int i = 0; i < mOnProximityNegativeMessages; i++) {
+    private boolean releaseProxNegativeSuspendBlocker() {
+        if (mIsProximityNegativeAcquired) {
             mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+            mIsProximityNegativeAcquired = false;
+            return true;
         }
-        mOnProximityNegativeMessages = 0;
+        return false;
     }
 
     /**
      * Acquires the proximity debounce wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public void acquireProxDebounceSuspendBlocker() {
+    private boolean acquireProxDebounceSuspendBlocker() {
         if (!mHasProximityDebounced) {
             mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
+            mHasProximityDebounced = true;
+            return true;
         }
-        mHasProximityDebounced = true;
+        return false;
     }
 
     /**
      * Releases the proximity debounce wakelock and notifies the PowerManagerService about the
      * changes.
      */
-    public boolean releaseProxDebounceSuspendBlocker() {
+    private boolean releaseProxDebounceSuspendBlocker() {
         if (mHasProximityDebounced) {
             mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
             mHasProximityDebounced = false;
@@ -210,9 +315,11 @@
      */
     public Runnable getOnProximityPositiveRunnable() {
         return () -> {
-            mOnProximityPositiveMessages--;
-            mDisplayPowerCallbacks.onProximityPositive();
-            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+            if (mIsProximityPositiveAcquired) {
+                mIsProximityPositiveAcquired = false;
+                mDisplayPowerCallbacks.onProximityPositive();
+                mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+            }
         };
     }
 
@@ -221,9 +328,11 @@
      */
     public Runnable getOnStateChangedRunnable() {
         return () -> {
-            mOnStateChangedPending = false;
-            mDisplayPowerCallbacks.onStateChanged();
-            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+            if (mOnStateChangedPending) {
+                mOnStateChangedPending = false;
+                mDisplayPowerCallbacks.onStateChanged();
+                mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+            }
         };
     }
 
@@ -232,9 +341,11 @@
      */
     public Runnable getOnProximityNegativeRunnable() {
         return () -> {
-            mOnProximityNegativeMessages--;
-            mDisplayPowerCallbacks.onProximityNegative();
-            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+            if (mIsProximityNegativeAcquired) {
+                mIsProximityNegativeAcquired = false;
+                mDisplayPowerCallbacks.onProximityNegative();
+                mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+            }
         };
     }
 
@@ -246,8 +357,8 @@
         pw.println("  mDisplayId=" + mDisplayId);
         pw.println("  mUnfinishedBusiness=" + hasUnfinishedBusiness());
         pw.println("  mOnStateChangePending=" + isOnStateChangedPending());
-        pw.println("  mOnProximityPositiveMessages=" + getOnProximityPositiveMessages());
-        pw.println("  mOnProximityNegativeMessages=" + getOnProximityNegativeMessages());
+        pw.println("  mOnProximityPositiveMessages=" + isProximityPositiveAcquired());
+        pw.println("  mOnProximityNegativeMessages=" + isProximityNegativeAcquired());
     }
 
     @VisibleForTesting
@@ -286,13 +397,13 @@
     }
 
     @VisibleForTesting
-    int getOnProximityPositiveMessages() {
-        return mOnProximityPositiveMessages;
+    boolean isProximityPositiveAcquired() {
+        return mIsProximityPositiveAcquired;
     }
 
     @VisibleForTesting
-    int getOnProximityNegativeMessages() {
-        return mOnProximityNegativeMessages;
+    boolean isProximityNegativeAcquired() {
+        return mIsProximityNegativeAcquired;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 346f311..58428ca 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -43,6 +43,7 @@
 
 import java.io.FileDescriptor;
 import java.util.Objects;
+import java.util.OptionalInt;
 
 /**
  * Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -60,6 +61,9 @@
     private final TelephonyManager mTelephonyManager;
     private final ArraySet<String> mBugreportWhitelistedPackages;
 
+    @GuardedBy("mLock")
+    private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
+
     BugreportManagerServiceImpl(Context context) {
         mContext = context;
         mAppOps = context.getSystemService(AppOpsManager.class);
@@ -70,13 +74,25 @@
 
     @Override
     @RequiresPermission(android.Manifest.permission.DUMP)
+    public void preDumpUiData(String callingPackage) {
+        enforcePermission(callingPackage, Binder.getCallingUid(), true);
+
+        synchronized (mLock) {
+            preDumpUiDataLocked(callingPackage);
+        }
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
     public void startBugreport(int callingUidUnused, String callingPackage,
             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
-            int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
+            int bugreportMode, int bugreportFlags, IDumpstateListener listener,
+            boolean isScreenshotRequested) {
         Objects.requireNonNull(callingPackage);
         Objects.requireNonNull(bugreportFd);
         Objects.requireNonNull(listener);
         validateBugreportMode(bugreportMode);
+        validateBugreportFlags(bugreportFlags);
 
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, bugreportMode
@@ -90,7 +106,7 @@
 
         synchronized (mLock) {
             startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
-                    bugreportMode, listener, isScreenshotRequested);
+                    bugreportMode, bugreportFlags, listener, isScreenshotRequested);
         }
     }
 
@@ -108,17 +124,14 @@
             }
             try {
                 // Note: this may throw SecurityException back out to the caller if they aren't
-                // allowed to cancel the report, in which case we should NOT be setting ctl.stop,
-                // since that would unintentionally kill some other app's bugreport, which we
-                // specifically disallow.
+                // allowed to cancel the report, in which case we should NOT stop the dumpstate
+                // service, since that would unintentionally kill some other app's bugreport, which
+                // we specifically disallow.
                 ds.cancelBugreport(callingUid, callingPackage);
             } catch (RemoteException e) {
                 Slog.e(TAG, "RemoteException in cancelBugreport", e);
             }
-            // This tells init to cancel bugreportd service. Note that this is achieved through
-            // setting a system property which is not thread-safe. So the lock here offers
-            // thread-safety only among callers of the API.
-            SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+            stopDumpstateBinderServiceLocked();
         }
     }
 
@@ -134,6 +147,14 @@
         }
     }
 
+    private void validateBugreportFlags(int flags) {
+        flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+        if (flags != 0) {
+            Slog.w(TAG, "Unknown bugreport flags: " + flags);
+            throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
+        }
+    }
+
     private void enforcePermission(
             String callingPackage, int callingUid, boolean checkCarrierPrivileges) {
         mAppOps.checkPackage(callingUid, callingPackage);
@@ -224,17 +245,62 @@
     }
 
     @GuardedBy("mLock")
+    private void preDumpUiDataLocked(String callingPackage) {
+        mPreDumpedDataUid = OptionalInt.empty();
+
+        if (isDumpstateBinderServiceRunningLocked()) {
+            Slog.e(TAG, "'dumpstate' is already running. "
+                    + "Cannot pre-dump data while another operation is currently in progress.");
+            return;
+        }
+
+        IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
+        if (ds == null) {
+            Slog.e(TAG, "Unable to get bugreport service");
+            return;
+        }
+
+        try {
+            ds.preDumpUiData(callingPackage);
+        } catch (RemoteException e) {
+            return;
+        } finally {
+            // dumpstate service is already started now. We need to kill it to manage the
+            // lifecycle correctly. If we don't subsequent callers will get
+            // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
+            stopDumpstateBinderServiceLocked();
+        }
+
+
+        mPreDumpedDataUid = OptionalInt.of(Binder.getCallingUid());
+    }
+
+    @GuardedBy("mLock")
     private void startBugreportLocked(int callingUid, String callingPackage,
             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
-            int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
+            int bugreportMode, int bugreportFlags, IDumpstateListener listener,
+            boolean isScreenshotRequested) {
         if (isDumpstateBinderServiceRunningLocked()) {
             Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
-                    + " while another one is currently in progress.");
-            reportError(listener,
-                    IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+                    + " while another operation is currently in progress.");
+            reportError(listener, IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
             return;
         }
 
+        if ((bugreportFlags & BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA) != 0) {
+            if (mPreDumpedDataUid.isEmpty()) {
+                bugreportFlags = clearBugreportFlag(bugreportFlags,
+                        BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+                Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
+                        + " No pre-dumped data is available.");
+            } else if (mPreDumpedDataUid.getAsInt() != callingUid) {
+                bugreportFlags = clearBugreportFlag(bugreportFlags,
+                        BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+                Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
+                        + " Data was pre-dumped by a different UID.");
+            }
+        }
+
         IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
         if (ds == null) {
             Slog.w(TAG, "Unable to get bugreport service");
@@ -245,10 +311,10 @@
         // Wrap the listener so we can intercept binder events directly.
         IDumpstateListener myListener = new DumpstateListener(listener, ds);
         try {
-            ds.startBugreport(callingUid, callingPackage,
-                    bugreportFd, screenshotFd, bugreportMode, myListener, isScreenshotRequested);
+            ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
+                    bugreportFlags, myListener, isScreenshotRequested);
         } catch (RemoteException e) {
-            // bugreportd service is already started now. We need to kill it to manage the
+            // dumpstate service is already started now. We need to kill it to manage the
             // lifecycle correctly. If we don't subsequent callers will get
             // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
             // Note that listener will be notified by the death recipient below.
@@ -309,6 +375,19 @@
         return ds;
     }
 
+    @GuardedBy("mLock")
+    private void stopDumpstateBinderServiceLocked() {
+        // This tells init to cancel bugreportd service. Note that this is achieved through
+        // setting a system property which is not thread-safe. So the lock here offers
+        // thread-safety only among callers of the API.
+        SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+    }
+
+    private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
+        flags &= ~flag;
+        return flags;
+    }
+
     private void reportError(IDumpstateListener listener, int errorCode) {
         try {
             listener.onError(errorCode);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index be3a4da..6b31555 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -1005,11 +1006,14 @@
     /** @see ApplicationInfo#privateFlagsExt */
     public static int appInfoPrivateFlagsExt(AndroidPackage pkg,
                                              @Nullable PackageStateInternal pkgSetting) {
+        var isAllowlistedForHiddenApis = SystemConfig.getInstance().getHiddenApiWhitelistedApps()
+                .contains(pkg.getPackageName());
         // @formatter:off
         int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
                 | flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
                 | flag(pkg.areAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
-                | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK);
+                | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK)
+                | flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
         return appInfoPrivateFlagsExt(pkgWithoutStateFlags, pkgSetting);
         // @formatter:on
     }
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index cb52e5f..6e82907 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -101,6 +101,13 @@
             }
             return enabledComponent;
         }
+
+        @Override
+        public List<ComponentName> getDefaultVrComponents() {
+            getContext().enforceCallingOrSelfPermission(Manifest.permission.QUERY_ALL_PACKAGES,
+                    "Caller must hold " + Manifest.permission.QUERY_ALL_PACKAGES);
+            return new ArrayList<>(SystemConfig.getInstance().getDefaultVrComponents());
+        }
     };
 
     public SystemConfigService(Context context) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 2df6823a..20af02e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -177,15 +177,19 @@
         advanceTime(1);
 
         // two times, one for unfinished business and one for proximity
-        verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
-        verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
 
 
         dpc.stop();
         advanceTime(1);
         // two times, one for unfinished business and one for proximity
-        verify(mWakelockController).acquireUnfinishedBusinessSuspendBlocker();
-        verify(mWakelockController).acquireProxDebounceSuspendBlocker();
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
index 288408c..07a81ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.hardware.display.DisplayManagerInternal;
 
@@ -33,6 +34,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.Callable;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class WakelockControllerTest {
@@ -64,25 +67,18 @@
     }
 
     @Test
-    public void acquireStateChangedSuspendBlockerAcquiresIfNotAcquired() {
-        // Acquire the suspend blocker
-        assertTrue(mWakelockController.acquireStateChangedSuspendBlocker());
-        assertTrue(mWakelockController.isOnStateChangedPending());
-
-        // Try to reacquire
-        assertFalse(mWakelockController.acquireStateChangedSuspendBlocker());
-        assertTrue(mWakelockController.isOnStateChangedPending());
+    public void acquireStateChangedSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+        // Acquire
+        verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_STATE_CHANGED,
+                () -> mWakelockController.isOnStateChangedPending());
 
         // Verify acquire happened only once
         verify(mDisplayPowerCallbacks, times(1))
                 .acquireSuspendBlocker(mWakelockController.getSuspendBlockerOnStateChangedId());
 
         // Release
-        mWakelockController.releaseStateChangedSuspendBlocker();
-        assertFalse(mWakelockController.isOnStateChangedPending());
-
-        // Try to release again
-        mWakelockController.releaseStateChangedSuspendBlocker();
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_STATE_CHANGED,
+                () -> mWakelockController.isOnStateChangedPending());
 
         // Verify release happened only once
         verify(mDisplayPowerCallbacks, times(1))
@@ -90,25 +86,18 @@
     }
 
     @Test
-    public void acquireUnfinishedBusinessSuspendBlockerAcquiresIfNotAcquired() {
-        // Acquire the suspend blocker
-        mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
-        assertTrue(mWakelockController.hasUnfinishedBusiness());
-
-        // Try to reacquire
-        mWakelockController.acquireUnfinishedBusinessSuspendBlocker();
-        assertTrue(mWakelockController.hasUnfinishedBusiness());
+    public void acquireUnfinishedBusinessSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+        // Acquire
+        verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS,
+                () -> mWakelockController.hasUnfinishedBusiness());
 
         // Verify acquire happened only once
         verify(mDisplayPowerCallbacks, times(1))
                 .acquireSuspendBlocker(mWakelockController.getSuspendBlockerUnfinishedBusinessId());
 
-        // Release the suspend blocker
-        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
-        assertFalse(mWakelockController.hasUnfinishedBusiness());
-
-        // Try to release again
-        mWakelockController.releaseUnfinishedBusinessSuspendBlocker();
+        // Release
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS,
+                () -> mWakelockController.hasUnfinishedBusiness());
 
         // Verify release happened only once
         verify(mDisplayPowerCallbacks, times(1))
@@ -116,70 +105,56 @@
     }
 
     @Test
-    public void acquireProxPositiveSuspendBlockerAcquiresIfNotAcquired() {
-        // Acquire the suspend blocker
-        mWakelockController.acquireProxPositiveSuspendBlocker();
-        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 1);
-
-        // Try to reacquire
-        mWakelockController.acquireProxPositiveSuspendBlocker();
-        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 2);
+    public void acquireProxPositiveSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+        // Acquire
+        verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE,
+                () -> mWakelockController.isProximityPositiveAcquired());
 
         // Verify acquire happened only once
-        verify(mDisplayPowerCallbacks, times(2))
+        verify(mDisplayPowerCallbacks, times(1))
                 .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
 
-        // Release the suspend blocker
-        mWakelockController.releaseProxPositiveSuspendBlocker();
-        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 0);
+        // Release
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE,
+                () -> mWakelockController.isProximityPositiveAcquired());
 
-        // Verify all suspend blockers were released
-        verify(mDisplayPowerCallbacks, times(2))
+        // Verify release happened only once
+        verify(mDisplayPowerCallbacks, times(1))
                 .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxPositiveId());
     }
 
     @Test
-    public void acquireProxNegativeSuspendBlockerAcquiresIfNotAcquired() {
-        // Acquire the suspend blocker
-        mWakelockController.acquireProxNegativeSuspendBlocker();
-        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 1);
-
-        // Try to reacquire
-        mWakelockController.acquireProxNegativeSuspendBlocker();
-        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 2);
+    public void acquireProxNegativeSuspendBlockerAcquiresIfNotAcquired() throws Exception {
+        // Acquire
+        verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE,
+                () -> mWakelockController.isProximityNegativeAcquired());
 
         // Verify acquire happened only once
-        verify(mDisplayPowerCallbacks, times(2))
+        verify(mDisplayPowerCallbacks, times(1))
                 .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
 
-        // Release the suspend blocker
-        mWakelockController.releaseProxNegativeSuspendBlocker();
-        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 0);
+        // Release
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE,
+                () -> mWakelockController.isProximityNegativeAcquired());
 
-        // Verify all suspend blockers were released
-        verify(mDisplayPowerCallbacks, times(2))
+        // Verify release happened only once
+        verify(mDisplayPowerCallbacks, times(1))
                 .releaseSuspendBlocker(mWakelockController.getSuspendBlockerProxNegativeId());
     }
 
     @Test
-    public void acquireProxDebounceSuspendBlockerAcquiresIfNotAcquired() {
+    public void acquireProxDebounceSuspendBlockerAcquiresIfNotAcquired() throws Exception {
         // Acquire the suspend blocker
-        mWakelockController.acquireProxDebounceSuspendBlocker();
-
-        // Try to reacquire
-        mWakelockController.acquireProxDebounceSuspendBlocker();
-        assertTrue(mWakelockController.hasProximitySensorDebounced());
+        verifyWakelockAcquisitionAndReaquisition(WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE,
+                () -> mWakelockController.hasProximitySensorDebounced());
 
         // Verify acquire happened only once
         verify(mDisplayPowerCallbacks, times(1))
                 .acquireSuspendBlocker(mWakelockController.getSuspendBlockerProxDebounceId());
 
         // Release the suspend blocker
-        assertTrue(mWakelockController.releaseProxDebounceSuspendBlocker());
-
-        // Release again
-        assertFalse(mWakelockController.releaseProxDebounceSuspendBlocker());
-        assertFalse(mWakelockController.hasProximitySensorDebounced());
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE,
+                () -> mWakelockController.hasProximitySensorDebounced());
 
         // Verify suspend blocker was released only once
         verify(mDisplayPowerCallbacks, times(1))
@@ -189,52 +164,126 @@
     @Test
     public void proximityPositiveRunnableWorksAsExpected() {
         // Acquire the suspend blocker twice
-        mWakelockController.acquireProxPositiveSuspendBlocker();
-        mWakelockController.acquireProxPositiveSuspendBlocker();
+        assertTrue(mWakelockController.acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE));
 
         // Execute the runnable
         Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
         proximityPositiveRunnable.run();
 
         // Validate one suspend blocker was released
-        assertEquals(mWakelockController.getOnProximityPositiveMessages(), 1);
+        assertFalse(mWakelockController.isProximityPositiveAcquired());
         verify(mDisplayPowerCallbacks).onProximityPositive();
         verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
                 mWakelockController.getSuspendBlockerProxPositiveId());
     }
 
     @Test
+    public void proximityPositiveRunnableDoesNothingIfNotAcquired() {
+        // Execute the runnable
+        Runnable proximityPositiveRunnable = mWakelockController.getOnProximityPositiveRunnable();
+        proximityPositiveRunnable.run();
+
+        // Validate one suspend blocker was released
+        assertFalse(mWakelockController.isProximityPositiveAcquired());
+        verifyZeroInteractions(mDisplayPowerCallbacks);
+    }
+
+    @Test
     public void proximityNegativeRunnableWorksAsExpected() {
         // Acquire the suspend blocker twice
-        mWakelockController.acquireProxNegativeSuspendBlocker();
-        mWakelockController.acquireProxNegativeSuspendBlocker();
+        assertTrue(mWakelockController.acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE));
 
         // Execute the runnable
         Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
         proximityNegativeRunnable.run();
 
         // Validate one suspend blocker was released
-        assertEquals(mWakelockController.getOnProximityNegativeMessages(), 1);
+        assertFalse(mWakelockController.isProximityNegativeAcquired());
         verify(mDisplayPowerCallbacks).onProximityNegative();
         verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
                 mWakelockController.getSuspendBlockerProxNegativeId());
     }
 
     @Test
+    public void proximityNegativeRunnableDoesNothingIfNotAcquired() {
+        // Execute the runnable
+        Runnable proximityNegativeRunnable = mWakelockController.getOnProximityNegativeRunnable();
+        proximityNegativeRunnable.run();
+
+        // Validate one suspend blocker was released
+        assertFalse(mWakelockController.isProximityNegativeAcquired());
+        verifyZeroInteractions(mDisplayPowerCallbacks);
+    }
+
+    @Test
     public void onStateChangeRunnableWorksAsExpected() {
         // Acquire the suspend blocker twice
-        mWakelockController.acquireStateChangedSuspendBlocker();
+        assertTrue(mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_STATE_CHANGED));
 
         // Execute the runnable
         Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
         stateChangeRunnable.run();
 
         // Validate one suspend blocker was released
-        assertEquals(mWakelockController.isOnStateChangedPending(), false);
+        assertFalse(mWakelockController.isOnStateChangedPending());
         verify(mDisplayPowerCallbacks).onStateChanged();
         verify(mDisplayPowerCallbacks).releaseSuspendBlocker(
                 mWakelockController.getSuspendBlockerOnStateChangedId());
     }
 
+    @Test
+    public void onStateChangeRunnableDoesNothingIfNotAcquired() {
+        // Execute the runnable
+        Runnable stateChangeRunnable = mWakelockController.getOnStateChangedRunnable();
+        stateChangeRunnable.run();
+
+        // Validate one suspend blocker was released
+        assertFalse(mWakelockController.isOnStateChangedPending());
+        verifyZeroInteractions(mDisplayPowerCallbacks);
+    }
+
+    private void verifyWakelockAcquisitionAndReaquisition(int wakelockId,
+            Callable<Boolean> isWakelockAcquiredCallable)
+            throws Exception {
+        verifyWakelockAcquisition(wakelockId, isWakelockAcquiredCallable);
+        verifyWakelockReacquisition(wakelockId, isWakelockAcquiredCallable);
+    }
+
+    private void verifyWakelockReleaseAndRerelease(int wakelockId,
+            Callable<Boolean> isWakelockAcquiredCallable)
+            throws Exception {
+        verifyWakelockRelease(wakelockId, isWakelockAcquiredCallable);
+        verifyWakelockRerelease(wakelockId, isWakelockAcquiredCallable);
+    }
+
+    private void verifyWakelockAcquisition(int wakelockId,
+            Callable<Boolean> isWakelockAcquiredCallable)
+            throws Exception {
+        assertTrue(mWakelockController.acquireWakelock(wakelockId));
+        assertTrue(isWakelockAcquiredCallable.call());
+    }
+
+    private void verifyWakelockReacquisition(int wakelockId,
+            Callable<Boolean> isWakelockAcquiredCallable)
+            throws Exception {
+        assertFalse(mWakelockController.acquireWakelock(wakelockId));
+        assertTrue(isWakelockAcquiredCallable.call());
+    }
+
+    private void verifyWakelockRelease(int wakelockId, Callable<Boolean> isWakelockAcquiredCallable)
+            throws Exception {
+        assertTrue(mWakelockController.releaseWakelock(wakelockId));
+        assertFalse(isWakelockAcquiredCallable.call());
+    }
+
+    private void verifyWakelockRerelease(int wakelockId,
+            Callable<Boolean> isWakelockAcquiredCallable)
+            throws Exception {
+        assertFalse(mWakelockController.releaseWakelock(wakelockId));
+        assertFalse(isWakelockAcquiredCallable.call());
+    }
+
 
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index b354c7b..f46877e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -16,30 +16,48 @@
 
 package com.android.server.job;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_EJ;
 import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_REGULAR;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+
 import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
 import com.android.server.LocalServices;
 import com.android.server.job.JobConcurrencyManager.GracePeriodObserver;
 import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
@@ -52,6 +70,12 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -64,10 +88,24 @@
     private int mDefaultUserId;
     private GracePeriodObserver mGracePeriodObserver;
     private Context mContext;
+    private InjectorForTest mInjector;
+    private MockitoSession mMockingSession;
     private Resources mResources;
     private PendingJobQueue mPendingJobQueue;
     private DeviceConfig.Properties.Builder mConfigBuilder;
 
+    @Mock
+    private IPackageManager mIPackageManager;
+
+    static class InjectorForTest extends JobConcurrencyManager.Injector {
+        @Override
+        JobServiceContext createJobServiceContext(JobSchedulerService service,
+                JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
+                JobPackageTracker tracker, Looper looper) {
+            return mock(JobServiceContext.class);
+        }
+    }
+
     @BeforeClass
     public static void setUpOnce() {
         LocalServices.addService(UserManagerInternal.class, mock(UserManagerInternal.class));
@@ -83,6 +121,11 @@
 
     @Before
     public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(AppGlobals.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
         final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
         mContext = mock(Context.class);
         mResources = mock(Resources.class);
@@ -93,7 +136,9 @@
         mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
         mPendingJobQueue = new PendingJobQueue();
         doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
-        mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService);
+        doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+        mInjector = new InjectorForTest();
+        mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService, mInjector);
         mGracePeriodObserver = mock(GracePeriodObserver.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -106,6 +151,74 @@
     @After
     public void tearDown() throws Exception {
         resetConfig();
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testPrepareForAssignmentDetermination_noJobs() {
+        mPendingJobQueue.clear();
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
+        assertEquals(0, preferredUidOnly.size());
+        assertEquals(0, stoppable.size());
+    }
+
+    @Test
+    public void testPrepareForAssignmentDetermination_onlyPendingJobs() {
+        final ArraySet<JobStatus> jobs = new ArraySet<>();
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+            JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+            mPendingJobQueue.add(job);
+            jobs.add(job);
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
+        assertEquals(0, preferredUidOnly.size());
+        assertEquals(0, stoppable.size());
+    }
+
+    @Test
+    public void testDetermineAssignments_allRegular() throws Exception {
+        setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+        final ArraySet<JobStatus> jobs = new ArraySet<>();
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+            final int uid = mDefaultUserId * UserHandle.PER_USER_RANGE + i;
+            final String sourcePkgName = "com.source.package." + UserHandle.getAppId(uid);
+            setPackageUid(sourcePkgName, uid);
+            final JobStatus job = createJob(uid, sourcePkgName);
+            mPendingJobQueue.add(job);
+            jobs.add(job);
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> changed = new ArraySet<>();
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
+        for (int i = changed.size() - 1; i >= 0; --i) {
+            jobs.remove(changed.valueAt(i).newJob);
+        }
+        assertTrue("Some jobs weren't assigned", jobs.isEmpty());
     }
 
     @Test
@@ -403,16 +516,58 @@
     }
 
     private static JobStatus createJob(int uid) {
-        return createJob(uid, 1);
+        return createJob(uid, 1, null);
+    }
+
+    private static JobStatus createJob(int uid, String sourcePackageName) {
+        return createJob(uid, 1, sourcePackageName);
     }
 
     private static JobStatus createJob(int uid, int jobId) {
-        return JobStatus.createFromJobInfo(
-                new JobInfo.Builder(jobId, new ComponentName("foo", "bar")).build(), uid,
-                null, UserHandle.getUserId(uid), "JobConcurrencyManagerTest");
+        return createJob(uid, jobId, null);
     }
 
-    private void setConcurrencyConfig(int total) throws Exception {
+    private static JobStatus createJob(int uid, int jobId, @Nullable String sourcePackageName) {
+        return JobStatus.createFromJobInfo(
+                new JobInfo.Builder(jobId, new ComponentName("foo", "bar")).build(), uid,
+                sourcePackageName, UserHandle.getUserId(uid), "JobConcurrencyManagerTest");
+    }
+
+    private static final class TypeConfig {
+        public final String workTypeString;
+        public final int min;
+        public final int max;
+
+        private TypeConfig(@JobConcurrencyManager.WorkType int workType, int min, int max) {
+            switch (workType) {
+                case WORK_TYPE_TOP:
+                    workTypeString = "top";
+                    break;
+                case WORK_TYPE_FGS:
+                    workTypeString = "fgs";
+                    break;
+                case WORK_TYPE_EJ:
+                    workTypeString = "ej";
+                    break;
+                case WORK_TYPE_BG:
+                    workTypeString = "bg";
+                    break;
+                case WORK_TYPE_BGUSER:
+                    workTypeString = "bguser";
+                    break;
+                case WORK_TYPE_BGUSER_IMPORTANT:
+                    workTypeString = "bguser_important";
+                    break;
+                case WORK_TYPE_NONE:
+                default:
+                    throw new IllegalArgumentException("invalid work type: " + workType);
+            }
+            this.min = min;
+            this.max = max;
+        }
+    }
+
+    private void setConcurrencyConfig(int total, TypeConfig... typeConfigs) throws Exception {
         // Set the values for all memory states so we don't have to worry about memory on the device
         // during testing.
         final String[] identifiers = {
@@ -422,10 +577,23 @@
         for (String identifier : identifiers) {
             mConfigBuilder
                     .setInt(WorkTypeConfig.KEY_PREFIX_MAX_TOTAL + identifier, total);
+            for (TypeConfig config : typeConfigs) {
+                mConfigBuilder.setInt(
+                        WorkTypeConfig.KEY_PREFIX_MAX + config.workTypeString + "_" + identifier,
+                        config.max);
+                mConfigBuilder.setInt(
+                        WorkTypeConfig.KEY_PREFIX_MIN + config.workTypeString + "_" + identifier,
+                        config.min);
+            }
         }
         updateDeviceConfig();
     }
 
+    private void setPackageUid(final String pkgName, final int uid) throws Exception {
+        doReturn(uid).when(mIPackageManager)
+                .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+    }
+
     private void updateDeviceConfig() throws Exception {
         DeviceConfig.setProperties(mConfigBuilder.build());
         mJobConcurrencyManager.updateConfigLocked();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 1d08a80..c58104a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -34,6 +34,7 @@
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
+import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
 import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -143,6 +144,7 @@
                 mPrefetchController);
         mFcConfig = mFlexibilityController.getFcConfig();
 
+        setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
     }
@@ -233,21 +235,22 @@
 
     @Test
     public void testOnConstantsUpdated_PercentsToDropConstraints() {
-        JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+        JobInfo.Builder jb = createJob(0)
+                .setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
         JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
-        assertEquals(150L,
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
         assertArrayEquals(
                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
                 new int[] {10, 20, 30, 40});
-        assertEquals(110L,
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         js.adjustNumRequiredFlexibleConstraints(-1);
-        assertEquals(120L,
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         js.adjustNumRequiredFlexibleConstraints(-1);
-        assertEquals(130L,
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
     }
 
@@ -274,24 +277,27 @@
         long nextTimeToDropNumConstraints;
 
         // no delay, deadline
-        JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000);
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
         JobStatus js = createJobStatus("time", jb);
 
         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
-        assertEquals(1000 + FROZEN_TIME, js.getLatestRunTimeElapsed());
+        assertEquals(MIN_WINDOW_FOR_FLEXIBILITY_MS + FROZEN_TIME, js.getLatestRunTimeElapsed());
         assertEquals(FROZEN_TIME, js.enqueueTime);
 
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(600L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+                nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(700L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+                nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(800L, nextTimeToDropNumConstraints);
+        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+                nextTimeToDropNumConstraints);
 
         // delay, no deadline
         jb = createJob(0).setMinimumLatency(800000L);
@@ -326,20 +332,26 @@
         assertEquals(181440100L, nextTimeToDropNumConstraints);
 
         // delay, deadline
-        jb = createJob(0).setOverrideDeadline(1100).setMinimumLatency(100);
+        jb = createJob(0)
+                .setOverrideDeadline(2 * MIN_WINDOW_FOR_FLEXIBILITY_MS)
+                .setMinimumLatency(MIN_WINDOW_FOR_FLEXIBILITY_MS);
         js = createJobStatus("time", jb);
 
+        final long windowStart = FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS;
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(700L, nextTimeToDropNumConstraints);
+        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+                nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(800L, nextTimeToDropNumConstraints);
+        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+                nextTimeToDropNumConstraints);
         js.adjustNumRequiredFlexibleConstraints(-1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(900L, nextTimeToDropNumConstraints);
+        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+                nextTimeToDropNumConstraints);
     }
 
     @Test
@@ -734,10 +746,9 @@
 
     @Test
     public void testResetJobNumDroppedConstraints() {
-        JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
+        JobInfo.Builder jb = createJob(22);
         JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
-        js.adjustNumRequiredFlexibleConstraints(3);
-        long nowElapsed;
+        long nowElapsed = FROZEN_TIME;
 
         mFlexibilityController.mFlexibilityTracker.add(js);
 
@@ -746,8 +757,7 @@
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
 
-
-        nowElapsed = 155L;
+        nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
@@ -766,7 +776,7 @@
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
 
-        nowElapsed = 140L;
+        nowElapsed = FROZEN_TIME;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
@@ -777,7 +787,7 @@
         assertEquals(1, mFlexibilityController
                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
 
-        nowElapsed = 175L;
+        nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
@@ -786,7 +796,7 @@
         assertEquals(0, js.getNumRequiredFlexibleConstraints());
         assertEquals(3, js.getNumDroppedFlexibleConstraints());
 
-        nowElapsed = 165L;
+        nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index cc57b9f..dc7bcd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -38,6 +38,7 @@
 import android.os.incremental.IncrementalManager
 import android.provider.DeviceConfig
 import android.util.ArrayMap
+import android.util.ArraySet
 import android.util.DisplayMetrics
 import android.util.EventLog
 import android.view.Display
@@ -294,6 +295,8 @@
         wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
         whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
         whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
+        whenever(mocks.systemConfig.defaultVrComponents).thenReturn(ArraySet())
+        whenever(mocks.systemConfig.hiddenApiWhitelistedApps).thenReturn(ArraySet())
         wheneverStatic { SystemProperties.getBoolean("fw.free_cache_v2", true) }.thenReturn(true)
         wheneverStatic { Environment.getPackageCacheDirectory() }.thenReturn(packageCacheDirectory)
         wheneverStatic { SystemProperties.digestOf("ro.build.fingerprint") }.thenReturn("cacheName")
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index bccd8a0b..9ae8922 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -61,6 +62,7 @@
 public class UserBackupManagerServiceTest {
     private static final String TEST_PACKAGE = "package1";
     private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
+    private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
 
     @Mock Context mContext;
     @Mock IBackupManagerMonitor mBackupManagerMonitor;
@@ -179,6 +181,7 @@
 
         mService.agentDisconnected("com.android.foo");
 
+        mService.waitForAsyncOperation();
         verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class));
         verify(mOperationStorage).cancelOperation(eq(456), eq(true), any());
         verify(mOperationStorage).cancelOperation(eq(789), eq(true), any());
@@ -207,6 +210,8 @@
         boolean isEnabledStatePersisted = false;
         boolean shouldUseNewBackupEligibilityRules = false;
 
+        private volatile Thread mWorkerThread = null;
+
         TestBackupService(Context context, PackageManager packageManager,
                 LifecycleOperationStorage operationStorage) {
             super(context, packageManager, operationStorage);
@@ -229,5 +234,23 @@
         boolean shouldUseNewBackupEligibilityRules() {
             return shouldUseNewBackupEligibilityRules;
         }
+
+        @Override
+        Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
+            mWorkerThread = super.getThreadForAsyncOperation(operationName, operation);
+            return mWorkerThread;
+        }
+
+        private void waitForAsyncOperation() {
+            if (mWorkerThread == null) {
+                return;
+            }
+
+            try {
+                mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS);
+            } catch (InterruptedException e) {
+                fail("Failed waiting for worker thread to complete: " + e.getMessage());
+            }
+        }
     }
 }
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 79ab009..5179bab 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -27,7 +27,6 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import java.io.PrintWriter;
@@ -195,57 +194,20 @@
     }
 
     /**
-     * Convert display name source to string.
+     * Convert mobile data policy to string.
      *
-     * @param source The display name source.
-     * @return The display name source in string format.
+     * @param mobileDataPolicy The mobile data policy.
+     * @return The mobile data policy in string format.
      */
-    @NonNull
-    public static String displayNameSourceToString(
-            @SubscriptionManager.SimDisplayNameSource int source) {
-        switch (source) {
-            case SubscriptionManager.NAME_SOURCE_UNKNOWN: return "UNKNOWN";
-            case SubscriptionManager.NAME_SOURCE_CARRIER_ID: return "CARRIER_ID";
-            case SubscriptionManager.NAME_SOURCE_SIM_SPN: return "SIM_SPN";
-            case SubscriptionManager.NAME_SOURCE_USER_INPUT: return "USER_INPUT";
-            case SubscriptionManager.NAME_SOURCE_CARRIER: return "CARRIER";
-            case SubscriptionManager.NAME_SOURCE_SIM_PNN: return "SIM_PNN";
+    public static @NonNull String mobileDataPolicyToString(
+            @TelephonyManager.MobileDataPolicy int mobileDataPolicy) {
+        switch (mobileDataPolicy) {
+            case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
+                return "DATA_ON_NON_DEFAULT_DURING_VOICE_CALL";
+            case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
+                return "MMS_ALWAYS_ALLOWED";
             default:
-                return "UNKNOWN(" + source + ")";
-        }
-    }
-
-    /**
-     * Convert subscription type to string.
-     *
-     * @param type The subscription type.
-     * @return The subscription type in string format.
-     */
-    @NonNull
-    public static String subscriptionTypeToString(@SubscriptionManager.SubscriptionType int type) {
-        switch (type) {
-            case SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM: return "LOCAL_SIM";
-            case SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM: return "REMOTE_SIM";
-            default:
-                return "UNKNOWN(" + type + ")";
-        }
-    }
-
-    /**
-     * Convert usage setting to string.
-     *
-     * @param usageSetting Usage setting.
-     * @return The usage setting in string format.
-     */
-    @NonNull
-    public static String usageSettingToString(@SubscriptionManager.UsageSetting int usageSetting) {
-        switch (usageSetting) {
-            case SubscriptionManager.USAGE_SETTING_UNKNOWN: return "UNKNOWN";
-            case SubscriptionManager.USAGE_SETTING_DEFAULT: return "DEFAULT";
-            case SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC: return "VOICE_CENTRIC";
-            case SubscriptionManager.USAGE_SETTING_DATA_CENTRIC: return "DATA_CENTRIC";
-            default:
-                return "UNKNOWN(" + usageSetting + ")";
+                return "UNKNOWN(" + mobileDataPolicy + ")";
         }
     }
 }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index e0145e6..c2b65f8 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -1092,6 +1092,11 @@
 
         if (!TextUtils.isEmpty(simOperator)) {
             for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+                if (currentConfig == null) {
+                    Rlog.w("SmsMessage", "hasEmsSupport currentConfig is null");
+                    continue;
+                }
+
                 if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
                         (TextUtils.isEmpty(currentConfig.mGid1) ||
                                 (!TextUtils.isEmpty(currentConfig.mGid1) &&
@@ -1155,18 +1160,21 @@
     private static boolean mIsNoEmsSupportConfigListLoaded = false;
 
     private static boolean isNoEmsSupportConfigListExisted() {
-        if (!mIsNoEmsSupportConfigListLoaded) {
-            Resources r = Resources.getSystem();
-            if (r != null) {
-                String[] listArray = r.getStringArray(
-                        com.android.internal.R.array.no_ems_support_sim_operators);
-                if ((listArray != null) && (listArray.length > 0)) {
-                    mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
-                    for (int i=0; i<listArray.length; i++) {
-                        mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
+        synchronized (SmsMessage.class) {
+            if (!mIsNoEmsSupportConfigListLoaded) {
+                Resources r = Resources.getSystem();
+                if (r != null) {
+                    String[] listArray = r.getStringArray(
+                            com.android.internal.R.array.no_ems_support_sim_operators);
+                    if ((listArray != null) && (listArray.length > 0)) {
+                        mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
+                        for (int i = 0; i < listArray.length; i++) {
+                            mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(
+                                    listArray[i].split(";"));
+                        }
                     }
+                    mIsNoEmsSupportConfigListLoaded = true;
                 }
-                mIsNoEmsSupportConfigListLoaded = true;
             }
         }
 
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 0d3c80f..e055f63 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -974,7 +974,7 @@
                 + " groupOwner=" + mGroupOwner
                 + " isGroupDisabled=" + mIsGroupDisabled
                 + " displayNameSource="
-                + TelephonyUtils.displayNameSourceToString(mDisplayNameSource)
+                + SubscriptionManager.displayNameSourceToString(mDisplayNameSource)
                 + " iconTint=" + mIconTint
                 + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
                 + " dataRoaming=" + mDataRoaming
@@ -988,9 +988,9 @@
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
                 + " countryIso=" + mCountryIso
                 + " profileClass=" + mProfileClass
-                + " mType=" + TelephonyUtils.subscriptionTypeToString(mType)
+                + " mType=" + SubscriptionManager.subscriptionTypeToString(mType)
                 + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
-                + " usageSetting=" + TelephonyUtils.usageSettingToString(mUsageSetting)
+                + " usageSetting=" + SubscriptionManager.usageSettingToString(mUsageSetting)
                 + "]";
     }
 
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6189b49..e6c6b62 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4092,4 +4092,65 @@
                 (iSub)-> iSub.setUsageSetting(
                         usageSetting, subscriptionId, mContext.getOpPackageName()));
     }
+
+    /**
+     * Convert display name source to string.
+     *
+     * @param source The display name source.
+     * @return The display name source in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String displayNameSourceToString(
+            @SubscriptionManager.SimDisplayNameSource int source) {
+        switch (source) {
+            case SubscriptionManager.NAME_SOURCE_UNKNOWN: return "UNKNOWN";
+            case SubscriptionManager.NAME_SOURCE_CARRIER_ID: return "CARRIER_ID";
+            case SubscriptionManager.NAME_SOURCE_SIM_SPN: return "SIM_SPN";
+            case SubscriptionManager.NAME_SOURCE_USER_INPUT: return "USER_INPUT";
+            case SubscriptionManager.NAME_SOURCE_CARRIER: return "CARRIER";
+            case SubscriptionManager.NAME_SOURCE_SIM_PNN: return "SIM_PNN";
+            default:
+                return "UNKNOWN(" + source + ")";
+        }
+    }
+
+    /**
+     * Convert subscription type to string.
+     *
+     * @param type The subscription type.
+     * @return The subscription type in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String subscriptionTypeToString(@SubscriptionManager.SubscriptionType int type) {
+        switch (type) {
+            case SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM: return "LOCAL_SIM";
+            case SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM: return "REMOTE_SIM";
+            default:
+                return "UNKNOWN(" + type + ")";
+        }
+    }
+
+    /**
+     * Convert usage setting to string.
+     *
+     * @param usageSetting Usage setting.
+     * @return The usage setting in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String usageSettingToString(@SubscriptionManager.UsageSetting int usageSetting) {
+        switch (usageSetting) {
+            case SubscriptionManager.USAGE_SETTING_UNKNOWN: return "UNKNOWN";
+            case SubscriptionManager.USAGE_SETTING_DEFAULT: return "DEFAULT";
+            case SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC: return "VOICE_CENTRIC";
+            case SubscriptionManager.USAGE_SETTING_DATA_CENTRIC: return "DATA_CENTRIC";
+            default:
+                return "UNKNOWN(" + usageSetting + ")";
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index e658c2e..caee4e2 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -72,6 +72,7 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "WIFI_MODE_", value = {
+            WIFI_MODE_UNKNOWN,
             WIFI_MODE_WIFI_ONLY,
             WIFI_MODE_CELLULAR_PREFERRED,
             WIFI_MODE_WIFI_PREFERRED
@@ -79,6 +80,12 @@
     public @interface WiFiCallingMode {}
 
     /**
+     * Wifi calling mode is unknown. This is for initialization only.
+     * @hide
+     */
+    public static final int WIFI_MODE_UNKNOWN = -1;
+
+    /**
      * Register for IMS over IWLAN if WiFi signal quality is high enough. Do not hand over to LTE
      * registration if signal quality degrades.
      */
@@ -1573,4 +1580,24 @@
                         .get());
         return binder;
     }
+
+    /**
+     * Convert Wi-Fi calling mode to string.
+     *
+     * @param mode Wi-Fi calling mode.
+     * @return The Wi-Fi calling mode in string format.
+     *
+     * @hide
+     */
+    @NonNull
+    public static String wifiCallingModeToString(@ImsMmTelManager.WiFiCallingMode int mode) {
+        switch (mode) {
+            case ImsMmTelManager.WIFI_MODE_UNKNOWN: return "UNKNOWN";
+            case ImsMmTelManager.WIFI_MODE_WIFI_ONLY: return "WIFI_ONLY";
+            case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED: return "CELLULAR_PREFERRED";
+            case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED: return "WIFI_PREFERRED";
+            default:
+                return "UNKNOWN(" + mode + ")";
+        }
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
index cb61e35..0837c00 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -24,6 +24,7 @@
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -47,96 +48,115 @@
     }
 
     @FlakyTest
+    @Test
     override fun appLayerReplacesLauncher() {
         super.appLayerReplacesLauncher()
     }
 
     @FlakyTest
+    @Test
     override fun appLayerBecomesVisible() {
         super.appLayerBecomesVisible()
     }
 
     @FlakyTest
+    @Test
     override fun appWindowBecomesTopWindow() {
         super.appWindowBecomesTopWindow()
     }
 
     @FlakyTest
+    @Test
     override fun appWindowBecomesVisible() {
         super.appWindowBecomesVisible()
     }
 
     @FlakyTest
+    @Test
     override fun appWindowIsTopWindowAtEnd() {
         super.appWindowIsTopWindowAtEnd()
     }
 
     @FlakyTest
+    @Test
     override fun appWindowReplacesLauncherAsTopWindow() {
         super.appWindowReplacesLauncherAsTopWindow()
     }
 
     @FlakyTest
+    @Test
     override fun entireScreenCovered() {
         super.entireScreenCovered()
     }
 
     @FlakyTest
+    @Test
     override fun navBarLayerIsVisibleAtStartAndEnd() {
         super.navBarLayerIsVisibleAtStartAndEnd()
     }
 
     @FlakyTest
+    @Test
     override fun navBarLayerPositionAtStartAndEnd() {
         super.navBarLayerPositionAtStartAndEnd()
     }
 
     @FlakyTest
+    @Test
     override fun navBarWindowIsAlwaysVisible() {
         super.navBarWindowIsAlwaysVisible()
     }
 
     @FlakyTest
+    @Test
     override fun statusBarLayerIsVisibleAtStartAndEnd() {
         super.statusBarLayerIsVisibleAtStartAndEnd()
     }
 
     @FlakyTest
+    @Test
     override fun statusBarLayerPositionAtStartAndEnd() {
         super.statusBarLayerPositionAtStartAndEnd()
     }
 
     @FlakyTest
+    @Test
     override fun statusBarWindowIsAlwaysVisible() {
         super.statusBarWindowIsAlwaysVisible()
     }
 
     @FlakyTest
+    @Test
     override fun taskBarLayerIsVisibleAtStartAndEnd() {
         super.taskBarLayerIsVisibleAtStartAndEnd()
     }
 
     @FlakyTest
+    @Test
     override fun taskBarWindowIsAlwaysVisible() {
         super.taskBarWindowIsAlwaysVisible()
     }
 
     @FlakyTest
+    @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
 
     @FlakyTest
+    @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
     }
 
     @FlakyTest
+    @Test
     override fun focusChanges() {
         super.focusChanges()
     }
 
     @FlakyTest
+    @Test
     override fun appWindowAsTopWindowAtEnd() {
         super.appWindowAsTopWindowAtEnd()
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 06486ca..08624ee 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.Instrumentation
 import android.app.WallpaperManager
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -26,8 +27,8 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -56,7 +57,8 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
-    private val testApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation)
+    private val testApp = NewTasksAppHelper(instrumentation)
+    private val simpleApp = SimpleAppHelper(instrumentation)
     private val wallpaper by lazy {
         getWallpaperPackage(instrumentation) ?: error("Unable to obtain wallpaper")
     }
@@ -76,7 +78,7 @@
      * Checks that the [wallpaper] window is never visible when performing task transitions. A solid
      * color background should be shown instead.
      */
-    @Postsubmit
+    @FlakyTest(bugId = 253617416)
     @Test
     fun wallpaperWindowIsNeverVisible() {
         testSpec.assertWm { this.isNonAppWindowInvisible(wallpaper) }
@@ -86,7 +88,7 @@
      * Checks that the [wallpaper] layer is never visible when performing task transitions. A solid
      * color background should be shown instead.
      */
-    @Postsubmit
+    @FlakyTest(bugId = 253617416)
     @Test
     fun wallpaperLayerIsNeverVisible() {
         testSpec.assertLayers {
@@ -116,7 +118,7 @@
     }
 
     /** Checks that a color background is visible while the task transition is occurring. */
-    @Postsubmit
+    @FlakyTest(bugId = 240570652)
     @Test
     fun colorLayerIsVisibleDuringTransition() {
         val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
@@ -124,7 +126,7 @@
 
         testSpec.assertLayers {
             this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
-                    it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+                    it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
                 }
                 .isInvisible(bgColorLayer)
                 .then()
@@ -133,7 +135,7 @@
                 .then()
                 // Fully transitioned to simple SIMPLE_ACTIVITY
                 .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
-                    it.visibleRegion(SIMPLE_ACTIVITY).coversExactly(displayBounds)
+                    it.visibleRegion(simpleApp.componentMatcher).coversExactly(displayBounds)
                 }
                 .isInvisible(bgColorLayer)
                 .then()
@@ -142,7 +144,7 @@
                 .then()
                 // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
                 .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
-                    it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds)
+                    it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
                 }
                 .isInvisible(bgColorLayer)
         }
@@ -156,15 +158,15 @@
     @Test
     fun newTaskOpensOnTopAndThenCloses() {
         testSpec.assertWm {
-            this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+            this.isAppWindowOnTop(testApp.componentMatcher)
                 .then()
                 .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
                 .then()
-                .isAppWindowOnTop(SIMPLE_ACTIVITY)
+                .isAppWindowOnTop(simpleApp.componentMatcher)
                 .then()
                 .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
                 .then()
-                .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY)
+                .isAppWindowOnTop(testApp.componentMatcher)
         }
     }
 
@@ -225,10 +227,6 @@
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
     companion object {
-        private val LAUNCH_NEW_TASK_ACTIVITY =
-            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
-        private val SIMPLE_ACTIVITY = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
-
         private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
             val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
 
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index c84c2bc..92ea029 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -18,9 +18,8 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
 
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
@@ -30,19 +29,27 @@
 import android.os.Bundle;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests to verify the "auto show" behavior in {@code InputMethodManagerService} when the window
+ * gaining the focus to start the input.
+ */
 @RootPermissionTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public final class AutoShowTest {
 
     @Rule
@@ -52,18 +59,95 @@
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
 
+    private static final int[] SOFT_INPUT_VISIBILITY_FLAGS =
+            new int[] {
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED,
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN,
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE,
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE,
+            };
+
+    private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+            new int[] {
+                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED,
+                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE,
+                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN,
+                WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
+            };
+
+    // TODO(b/240359838): add test case {@code Configuration.SCREENLAYOUT_SIZE_LARGE}.
+    @Parameterized.Parameters(
+            name =
+                    "softInputVisibility={0}, softInputAdjustment={1},"
+                            + " softInputModeIsForwardNavigation={2}")
+    public static List<Object[]> softInputModeConfigs() {
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) {
+            for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+                params.add(new Object[] {softInputVisibility, softInputAdjust, true});
+                params.add(new Object[] {softInputVisibility, softInputAdjust, false});
+            }
+        }
+        return params;
+    }
+
+    private static final String SOFT_INPUT_FLAGS = "soft_input_flags";
+
+    private final int mSoftInputVisibility;
+    private final int mSoftInputAdjustment;
+    private final boolean mSoftInputIsForwardNavigation;
+
+    public AutoShowTest(
+            int softInputVisibility,
+            int softInputAdjustment,
+            boolean softInputIsForwardNavigation) {
+        mSoftInputVisibility = softInputVisibility;
+        mSoftInputAdjustment = softInputAdjustment;
+        mSoftInputIsForwardNavigation = softInputIsForwardNavigation;
+    }
+
     @Test
     public void autoShow() {
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        Intent intent = new Intent()
-                .setAction(Intent.ACTION_MAIN)
-                .setClass(instrumentation.getContext(), TestActivity.class)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        int flags = mSoftInputVisibility | mSoftInputAdjustment;
+        if (mSoftInputIsForwardNavigation) {
+            flags |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+        }
+
+        Intent intent =
+                new Intent()
+                        .setAction(Intent.ACTION_MAIN)
+                        .setClass(instrumentation.getContext(), TestActivity.class)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                        .putExtra(SOFT_INPUT_FLAGS, flags);
         TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
         EditText editText = activity.getEditText();
         waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
-        waitOnMainUntilImeIsShown(editText);
+
+        if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
+                || mSoftInputVisibility
+                        == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) {
+            // IME will be auto-shown if softInputMode is set with flag:
+            // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE
+            waitOnMainUntilImeIsShown(editText);
+        } else if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
+                || mSoftInputVisibility
+                        == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
+            // IME will be not be shown if softInputMode is set with flag:
+            // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN
+            verifyImeIsAlwaysHidden(editText);
+        } else {
+            // The current system behavior will choose to show IME automatically when navigating
+            // forward to an app that has no visibility state specified  (i.e.
+            // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag.
+            if (mSoftInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+                    && mSoftInputAdjustment == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+                    && mSoftInputIsForwardNavigation) {
+                waitOnMainUntilImeIsShown(editText);
+            }
+        }
     }
 
     public static class TestActivity extends Activity {
@@ -72,16 +156,15 @@
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            // IME will be auto-shown if the following conditions are met:
-            // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED.
-            // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE.
-            getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE);
+            int flags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0);
+            getWindow().setSoftInputMode(flags);
             LinearLayout rootView = new LinearLayout(this);
             rootView.setOrientation(LinearLayout.VERTICAL);
             mEditText = new EditText(this);
             rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
             setContentView(rootView);
-            // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true).
+            // Ensure the focused view is a text editor (View#onCheckIsTextEditor() returns true) to
+            // automatically display a soft input window.
             mEditText.requestFocus();
         }
 
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index ba2ba3c..b6d462c 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -25,6 +25,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ThrowingRunnable;
+
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -33,6 +35,7 @@
 public final class ImeStressTestUtil {
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long VERIFY_DURATION = TimeUnit.SECONDS.toMillis(2);
 
     private ImeStressTestUtil() {
     }
@@ -77,4 +80,41 @@
         eventually(() -> assertWithMessage("IME should be hidden").that(
                 callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT);
     }
+
+    /** Verify IME is always hidden within the given time duration. */
+    public static void verifyImeIsAlwaysHidden(View view) {
+        always(
+                () ->
+                        assertWithMessage("IME should be hidden")
+                                .that(callOnMainSync(() -> isImeShown(view)))
+                                .isFalse(),
+                VERIFY_DURATION);
+    }
+
+    /**
+     * Make sure that a {@link Runnable} always finishes without throwing a {@link Exception} in the
+     * given duration
+     *
+     * @param r The {@link Runnable} to run.
+     * @param timeoutMillis The number of milliseconds to wait for {@code r} to not throw
+     */
+    public static void always(ThrowingRunnable r, long timeoutMillis) {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                r.run();
+                if (System.currentTimeMillis() - start >= timeoutMillis) {
+                    return;
+                }
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException ignored) {
+                    // Do nothing
+                }
+            } catch (Throwable e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
 }