Merge "InputManager: Expose pilferPointers API"
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 9ac3e41..9d363c8 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -80,6 +80,7 @@
 import android.os.RemoteCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -619,7 +620,7 @@
         return blobInfos;
     }
 
-    private void deleteBlobInternal(long blobId, int callingUid) {
+    private void deleteBlobInternal(long blobId) {
         synchronized (mBlobsLock) {
             mBlobsMap.entrySet().removeIf(entry -> {
                 final BlobMetadata blobMetadata = entry.getValue();
@@ -1612,10 +1613,7 @@
         @Override
         @NonNull
         public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
-            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                throw new SecurityException("Only system uid is allowed to call "
-                        + "queryBlobsForUser()");
-            }
+            verifyCallerIsSystemUid("queryBlobsForUser");
 
             final int resolvedUserId = userId == USER_CURRENT
                     ? ActivityManager.getCurrentUser() : userId;
@@ -1629,13 +1627,9 @@
 
         @Override
         public void deleteBlob(long blobId) {
-            final int callingUid = Binder.getCallingUid();
-            if (callingUid != Process.SYSTEM_UID) {
-                throw new SecurityException("Only system uid is allowed to call "
-                        + "deleteBlob()");
-            }
+            verifyCallerIsSystemUid("deleteBlob");
 
-            deleteBlobInternal(blobId, callingUid);
+            deleteBlobInternal(blobId);
         }
 
         @Override
@@ -1716,6 +1710,18 @@
             return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this,
                     in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
         }
+
+        /**
+         * Verify if the caller is an admin user's app with system uid
+         */
+        private void verifyCallerIsSystemUid(final String operation) {
+            if (UserHandle.getCallingAppId() != Process.SYSTEM_UID
+                    || !mContext.getSystemService(UserManager.class)
+                    .isUserAdmin(UserHandle.getCallingUserId())) {
+                throw new SecurityException("Only admin user's app with system uid"
+                        + "are allowed to call #" + operation);
+            }
+        }
     }
 
     static final class DumpArgs {
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index bd475e9..e3bd5ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -31,6 +31,7 @@
 import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -740,8 +741,10 @@
         }
     };
 
-    private final BroadcastReceiver mIdleStartedDoneReceiver = new BroadcastReceiver() {
-        @Override public void onReceive(Context context, Intent intent) {
+    private final IIntentReceiver mIdleStartedDoneReceiver = new IIntentReceiver.Stub() {
+        @Override
+        public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+                boolean ordered, boolean sticky, int sendingUser) {
             // When coming out of a deep idle, we will add in some delay before we allow
             // the system to settle down and finish the maintenance window.  This is
             // to give a chance for any pending work to be scheduled.
@@ -1816,13 +1819,15 @@
                     }
                     if (deepChanged) {
                         incActiveIdleOps();
-                        getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
-                                null, mIdleStartedDoneReceiver, null, 0, null, null);
+                        mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
+                                mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
+                                null, null, null);
                     }
                     if (lightChanged) {
                         incActiveIdleOps();
-                        getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
-                                null, mIdleStartedDoneReceiver, null, 0, null, null);
+                        mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
+                                mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
+                                null, null, null);
                     }
                     // Always start with one active op for the message being sent here.
                     // Now we are done!
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 52dc01b..53f5c6e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job;
 
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
@@ -99,6 +101,18 @@
     static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = STANDARD_CONCURRENCY_LIMIT / 2;
+    @VisibleForTesting
+    static final String KEY_ENABLE_MAX_WAIT_TIME_BYPASS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "enable_max_wait_time_bypass";
+    private static final boolean DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS = true;
+    private static final String KEY_MAX_WAIT_EJ_MS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_ej_ms";
+    @VisibleForTesting
+    static final long DEFAULT_MAX_WAIT_EJ_MS = 5 * MINUTE_IN_MILLIS;
+    private static final String KEY_MAX_WAIT_REGULAR_MS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_regular_ms";
+    @VisibleForTesting
+    static final long DEFAULT_MAX_WAIT_REGULAR_MS = 30 * MINUTE_IN_MILLIS;
 
     /**
      * Set of possible execution types that a job can have. The actual type(s) of a job are based
@@ -353,6 +367,20 @@
      */
     private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
 
+    private boolean mMaxWaitTimeBypassEnabled = DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS;
+
+    /**
+     * The maximum time an expedited job would have to be potentially waiting for an available
+     * slot before we would consider creating a new slot for it.
+     */
+    private long mMaxWaitEjMs = DEFAULT_MAX_WAIT_EJ_MS;
+
+    /**
+     * The maximum time a regular job would have to be potentially waiting for an available
+     * slot before we would consider creating a new slot for it.
+     */
+    private long mMaxWaitRegularMs = DEFAULT_MAX_WAIT_REGULAR_MS;
+
     /** Current memory trim level. */
     private int mLastMemoryTrimLevel;
 
@@ -665,7 +693,7 @@
             return;
         }
 
-        prepareForAssignmentDeterminationLocked(
+        final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked(
                 mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
 
         if (DEBUG) {
@@ -674,7 +702,8 @@
         }
 
         determineAssignmentsLocked(
-                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+                mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+                minPreferredUidOnlyWaitingTimeMs);
 
         if (DEBUG) {
             Slog.d(TAG, printAssignments("running jobs final",
@@ -691,9 +720,10 @@
         noteConcurrency();
     }
 
+    /** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */
     @VisibleForTesting
     @GuardedBy("mLock")
-    void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
+    long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
             final List<ContextAssignment> stoppable) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
@@ -709,6 +739,8 @@
         updateNonRunningPrioritiesLocked(pendingJobQueue, true);
 
         final int numRunningJobs = activeServices.size();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        long minPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
         for (int i = 0; i < numRunningJobs; ++i) {
             final JobServiceContext jsc = activeServices.get(i);
             final JobStatus js = jsc.getRunningJobLocked();
@@ -729,6 +761,9 @@
             if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
                 stoppable.add(assignment);
             } else {
+                assignment.timeUntilStoppableMs = jsc.getRemainingGuaranteedTimeMs(nowElapsed);
+                minPreferredUidOnlyWaitingTimeMs =
+                        Math.min(minPreferredUidOnlyWaitingTimeMs, assignment.timeUntilStoppableMs);
                 preferredUidOnly.add(assignment);
             }
         }
@@ -754,6 +789,10 @@
         }
 
         mWorkCountTracker.onCountDone();
+        // Return 0 if there were no preferred UID only contexts to indicate no waiting time due
+        // to such jobs.
+        return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
+                ? 0 : minPreferredUidOnlyWaitingTimeMs;
     }
 
     @VisibleForTesting
@@ -761,12 +800,14 @@
     void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
             final ArraySet<ContextAssignment> idle,
             final List<ContextAssignment> preferredUidOnly,
-            final List<ContextAssignment> stoppable) {
+            final List<ContextAssignment> stoppable,
+            long minPreferredUidOnlyWaitingTimeMs) {
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
         final List<JobServiceContext> activeServices = mActiveServices;
         pendingJobQueue.resetIterator();
         JobStatus nextPending;
         int projectedRunningCount = activeServices.size();
+        long minChangedWaitingTimeMs = Long.MAX_VALUE;
         while ((nextPending = pendingJobQueue.next()) != null) {
             if (mRunningJobs.contains(nextPending)) {
                 // Should never happen.
@@ -785,6 +826,14 @@
                         + " to: " + nextPending);
             }
 
+            // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
+            // the number of additional contexts that are created due to long waiting times.
+            // By factoring it in, we imply that the new slot will be available for other
+            // pending jobs that could be designated as waiting too long, and those other jobs
+            // would only have to wait for the new slots to become available.
+            final long minWaitingTimeMs =
+                    Math.min(minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
+
             // Find an available slot for nextPending. The context should be one of the following:
             // 1. Unused
             // 2. Its job should have used up its minimum execution guarantee so it
@@ -833,6 +882,7 @@
                     //    app was on TOP, the app is still TOP, but there are too many TOP+EJs
                     //    running (because we don't want them to starve out other apps and the
                     //    current job has already run for the minimum guaranteed time).
+                    // 5. This new job could be waiting for too long for a slot to open up
                     boolean canReplace = isTopEj; // Case 1
                     if (!canReplace && !isInOverage) {
                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
@@ -840,6 +890,13 @@
                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
                                 || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
                     }
+                    if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
+                        if (nextPending.shouldTreatAsExpeditedJob()) {
+                            canReplace = minWaitingTimeMs >= mMaxWaitEjMs;
+                        } else {
+                            canReplace = minWaitingTimeMs >= mMaxWaitRegularMs;
+                        }
+                    }
                     if (canReplace) {
                         int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
                                 assignment.context.getRunningJobWorkType());
@@ -860,6 +917,7 @@
             }
             if (selectedContext == null && (!isInOverage || isTopEj)) {
                 int lowestBiasSeen = Integer.MAX_VALUE;
+                long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
                     final ContextAssignment assignment = preferredUidOnly.get(p);
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -872,6 +930,13 @@
                     }
 
                     if (selectedContext == null || lowestBiasSeen > jobBias) {
+                        if (selectedContext != null) {
+                            // We're no longer using the previous context, so factor it into the
+                            // calculation.
+                            newMinPreferredUidOnlyWaitingTimeMs = Math.min(
+                                    newMinPreferredUidOnlyWaitingTimeMs,
+                                    selectedContext.timeUntilStoppableMs);
+                        }
                         // Step down the preemption threshold - wind up replacing
                         // the lowest-bias running job
                         lowestBiasSeen = jobBias;
@@ -880,11 +945,17 @@
                         assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
                         // In this case, we're just going to preempt a low bias job, we're not
                         // actually starting a job, so don't set startingJob to true.
+                    } else {
+                        // We're not going to use this context, so factor it into the calculation.
+                        newMinPreferredUidOnlyWaitingTimeMs = Math.min(
+                                newMinPreferredUidOnlyWaitingTimeMs,
+                                assignment.timeUntilStoppableMs);
                     }
                 }
                 if (selectedContext != null) {
                     selectedContext.newJob = nextPending;
                     preferredUidOnly.remove(selectedContext);
+                    minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                 }
             }
             // Make sure to run EJs for the TOP app immediately.
@@ -901,6 +972,9 @@
                     selectedContext = null;
                 }
                 if (selectedContext == null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Allowing additional context because EJ would wait too long");
+                    }
                     selectedContext = mContextAssignmentPool.acquire();
                     if (selectedContext == null) {
                         selectedContext = new ContextAssignment();
@@ -913,6 +987,35 @@
                     selectedContext.newWorkType =
                             (workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
                 }
+            } else if (selectedContext == null && mMaxWaitTimeBypassEnabled) {
+                final boolean wouldBeWaitingTooLong = nextPending.shouldTreatAsExpeditedJob()
+                        ? minWaitingTimeMs >= mMaxWaitEjMs
+                        : minWaitingTimeMs >= mMaxWaitRegularMs;
+                if (wouldBeWaitingTooLong) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Allowing additional context because job would wait too long");
+                    }
+                    selectedContext = mContextAssignmentPool.acquire();
+                    if (selectedContext == null) {
+                        selectedContext = new ContextAssignment();
+                    }
+                    selectedContext.context = mIdleContexts.size() > 0
+                            ? mIdleContexts.removeAt(mIdleContexts.size() - 1)
+                            : createNewJobServiceContext();
+                    selectedContext.newJob = nextPending;
+                    final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
+                    if (workType != WORK_TYPE_NONE) {
+                        selectedContext.newWorkType = workType;
+                    } else {
+                        // Use the strongest work type possible for this job.
+                        for (int type = 1; type <= ALL_WORK_TYPES; type = type << 1) {
+                            if ((type & allWorkTypes) != 0) {
+                                selectedContext.newWorkType = type;
+                                break;
+                            }
+                        }
+                    }
+                }
             }
             final PackageStats packageStats = getPkgStatsLocked(
                     nextPending.getSourceUserId(), nextPending.getSourcePackageName());
@@ -923,6 +1026,8 @@
                 }
                 if (selectedContext.newJob != null) {
                     projectedRunningCount++;
+                    minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
+                            mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
                 }
                 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
             }
@@ -1251,13 +1356,37 @@
         }
 
         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
-        if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) {
+        if (pendingJobQueue.size() == 0) {
             worker.clearPreferredUid();
-            // We're over the limit (because the TOP app scheduled a lot of EJs). Don't start
-            // running anything new until we get back below the limit.
             noteConcurrency();
             return;
         }
+        if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT) {
+            final boolean respectConcurrencyLimit;
+            if (!mMaxWaitTimeBypassEnabled) {
+                respectConcurrencyLimit = true;
+            } else {
+                long minWaitingTimeMs = Long.MAX_VALUE;
+                final long nowElapsed = sElapsedRealtimeClock.millis();
+                for (int i = mActiveServices.size() - 1; i >= 0; --i) {
+                    minWaitingTimeMs = Math.min(minWaitingTimeMs,
+                            mActiveServices.get(i).getRemainingGuaranteedTimeMs(nowElapsed));
+                }
+                final boolean wouldBeWaitingTooLong =
+                        mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
+                                ? minWaitingTimeMs >= mMaxWaitEjMs
+                                : minWaitingTimeMs >= mMaxWaitRegularMs;
+                respectConcurrencyLimit = !wouldBeWaitingTooLong;
+            }
+            if (respectConcurrencyLimit) {
+                worker.clearPreferredUid();
+                // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+                // be able to stop the other jobs soon so don't start running anything new until we
+                // get back below the limit.
+                noteConcurrency();
+                return;
+            }
+        }
 
         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
             updateCounterConfigLocked();
@@ -1609,6 +1738,14 @@
         mPkgConcurrencyLimitRegular = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
                 properties.getInt(
                         KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
+
+        mMaxWaitTimeBypassEnabled = properties.getBoolean(
+                KEY_ENABLE_MAX_WAIT_TIME_BYPASS, DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS);
+        // EJ max wait must be in the range [0, infinity).
+        mMaxWaitEjMs = Math.max(0, properties.getLong(KEY_MAX_WAIT_EJ_MS, DEFAULT_MAX_WAIT_EJ_MS));
+        // Regular max wait must be in the range [EJ max wait, infinity).
+        mMaxWaitRegularMs = Math.max(mMaxWaitEjMs,
+                properties.getLong(KEY_MAX_WAIT_REGULAR_MS, DEFAULT_MAX_WAIT_REGULAR_MS));
     }
 
     @GuardedBy("mLock")
@@ -1622,6 +1759,9 @@
             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
             pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
             pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
+            pw.print(KEY_ENABLE_MAX_WAIT_TIME_BYPASS, mMaxWaitTimeBypassEnabled).println();
+            pw.print(KEY_MAX_WAIT_EJ_MS, mMaxWaitEjMs).println();
+            pw.print(KEY_MAX_WAIT_REGULAR_MS, mMaxWaitRegularMs).println();
             pw.println();
             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
             pw.println();
@@ -2382,6 +2522,7 @@
         public int workType = WORK_TYPE_NONE;
         public String preemptReason;
         public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
+        public long timeUntilStoppableMs;
         public String shouldStopJobReason;
         public JobStatus newJob;
         public int newWorkType = WORK_TYPE_NONE;
@@ -2392,6 +2533,7 @@
             workType = WORK_TYPE_NONE;
             preemptReason = null;
             preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
+            timeUntilStoppableMs = 0;
             shouldStopJobReason = null;
             newJob = null;
             newWorkType = WORK_TYPE_NONE;
@@ -2406,6 +2548,15 @@
         final PackageStats packageStats =
                 getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
         packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
+
+        final JobServiceContext context;
+        if (mIdleContexts.size() > 0) {
+            context = mIdleContexts.removeAt(mIdleContexts.size() - 1);
+        } else {
+            context = createNewJobServiceContext();
+        }
+        context.executeRunnableJob(job, mWorkCountTracker.canJobStart(getJobWorkTypes(job)));
+        mActiveServices.add(context);
     }
 
     @VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9e3f19d..7c61a35 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -454,6 +454,10 @@
         return mTimeoutElapsed;
     }
 
+    long getRemainingGuaranteedTimeMs(long nowElapsed) {
+        return Math.max(0, mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - nowElapsed);
+    }
+
     boolean isWithinExecutionGuaranteeTime() {
         return sElapsedRealtimeClock.millis()
                 < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
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 f9fb0d0..92716f4 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
@@ -1094,27 +1094,41 @@
 
     private void updateNetworkBytesLocked() {
         mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+        if (mTotalNetworkDownloadBytes < 0) {
+            // Legacy apps may have provided invalid negative values. Ignore invalid values.
+            mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+        }
         mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+        if (mTotalNetworkUploadBytes < 0) {
+            // Legacy apps may have provided invalid negative values. Ignore invalid values.
+            mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+        }
+        // Minimum network chunk bytes has had data validation since its introduction, so no
+        // need to do validation again.
         mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes();
 
         if (pendingWork != null) {
             for (int i = 0; i < pendingWork.size(); i++) {
-                if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                    // If any component of the job has unknown usage, we don't have a
-                    // complete picture of what data will be used, and we have to treat the
-                    // entire up/download as unknown.
-                    long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
-                    if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
+                if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN && downloadBytes > 0) {
+                    // If any component of the job has unknown usage, we won't have a
+                    // complete picture of what data will be used. However, we use what we are given
+                    // to get us as close to the complete picture as possible.
+                    if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
                         mTotalNetworkDownloadBytes += downloadBytes;
+                    } else {
+                        mTotalNetworkDownloadBytes = downloadBytes;
                     }
                 }
-                if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                    // If any component of the job has unknown usage, we don't have a
-                    // complete picture of what data will be used, and we have to treat the
-                    // entire up/download as unknown.
-                    long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
-                    if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
+                if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN && uploadBytes > 0) {
+                    // If any component of the job has unknown usage, we won't have a
+                    // complete picture of what data will be used. However, we use what we are given
+                    // to get us as close to the complete picture as possible.
+                    if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
                         mTotalNetworkUploadBytes += uploadBytes;
+                    } else {
+                        mTotalNetworkUploadBytes = uploadBytes;
                     }
                 }
                 final long chunkBytes = pendingWork.get(i).getMinimumNetworkChunkBytes();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index 5195f28..fc60228 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -31,6 +31,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.expresslog.Counter;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
@@ -88,6 +89,8 @@
             // will never be unsatisfied (our time base can not go backwards).
             final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
+                // We're intentionally excluding jobs whose deadlines have passed
+                // (mostly like deadlines of 0) when the job was scheduled.
                 return;
             } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
                     nowElapsedMillis)) {
@@ -158,6 +161,9 @@
                     // Scheduler.
                     mStateChangedListener.onRunJobNow(job);
                 }
+                mTrackedJobs.remove(job);
+                Counter.logIncrement(
+                        "job_scheduler.value_job_scheduler_job_deadline_expired_counter");
             } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
                 // This job's deadline is earlier than the current set alarm. Update the alarm.
                 setDeadlineExpiredAlarmLocked(job.getLatestRunTimeElapsed(),
@@ -169,8 +175,11 @@
                 && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
             // Since this is just the delay, we don't need to rush the Scheduler to run the job
             // immediately if the constraint is satisfied here.
-            if (!evaluateTimingDelayConstraint(job, nowElapsedMillis)
-                    && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+            if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
+                if (canStopTrackingJobLocked(job)) {
+                    mTrackedJobs.remove(job);
+                }
+            } else if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
                 // This job's delay is earlier than the current set alarm. Update the alarm.
                 setDelayExpiredAlarmLocked(job.getEarliestRunTime(),
                         mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
@@ -228,6 +237,8 @@
                         // Scheduler.
                         mStateChangedListener.onRunJobNow(job);
                     }
+                    Counter.logIncrement(
+                            "job_scheduler.value_job_scheduler_job_deadline_expired_counter");
                     it.remove();
                 } else {  // Sorted by expiry time, so take the next one and stop.
                     if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 581a545..17b8746 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -701,6 +701,10 @@
             return;
         }
         final long totalDischargeMah = mAnalyst.getBatteryScreenOffDischargeMah();
+        if (totalDischargeMah == 0) {
+            Slog.i(TAG, "Total discharge was 0");
+            return;
+        }
         final long batteryCapacityMah = mBatteryManagerInternal.getBatteryFullCharge() / 1000;
         final long estimatedLifeHours = batteryCapacityMah * totalScreenOffDurationMs
                 / totalDischargeMah / HOUR_IN_MILLIS;
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
index 3b14be7..24727c5 100644
--- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -107,7 +107,7 @@
                         DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
                 int rotation = display.getRotation();
                 Point size = new Point();
-                display.getSize(size);
+                display.getRealSize(size);
                 AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x,
                         size.y);
             }
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index ab198b3..488292d 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -139,7 +139,7 @@
                 serializer.attribute("", "id", Integer.toString(displayId));
                 int rotation = display.getRotation();
                 Point size = new Point();
-                display.getSize(size);
+                display.getRealSize(size);
                 for (int i = 0, n = windows.size(); i < n; ++i) {
                     dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation);
                 }
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index 6fd2bf2..1bcd343e 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -767,7 +767,7 @@
         if(root != null) {
             Display display = getAutomatorBridge().getDefaultDisplay();
             Point size = new Point();
-            display.getSize(size);
+            display.getRealSize(size);
             AccessibilityNodeInfoDumper.dumpWindowToFile(root,
                     new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName),
                     display.getRotation(), size.x, size.y);
diff --git a/core/api/current.txt b/core/api/current.txt
index 3174c28..a862ee0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18434,7 +18434,7 @@
     method public android.graphics.Point getLeftEyePosition();
     method public android.graphics.Point getMouthPosition();
     method public android.graphics.Point getRightEyePosition();
-    method public int getScore();
+    method @IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) public int getScore();
     field public static final int ID_UNSUPPORTED = -1; // 0xffffffff
     field public static final int SCORE_MAX = 100; // 0x64
     field public static final int SCORE_MIN = 1; // 0x1
@@ -18449,7 +18449,7 @@
     method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
     method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
     method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
-    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(@IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) int);
   }
 
   public final class InputConfiguration {
@@ -26541,6 +26541,7 @@
     method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
     method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
     method public void onMediaViewSizeChanged(@Px int, @Px int);
+    method public void onRecordingStarted(@NonNull String);
     method public abstract void onRelease();
     method public void onResetInteractiveApp();
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -26566,6 +26567,7 @@
     method @CallSuper public void requestCurrentChannelUri();
     method @CallSuper public void requestCurrentTvInputId();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
     method @CallSuper public void requestStreamVolume();
     method @CallSuper public void requestTrackInfoList();
     method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
@@ -26597,6 +26599,7 @@
     method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
     method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
     method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
+    method public void notifyRecordingStarted(@NonNull String);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -26638,6 +26641,7 @@
     method public void onRequestCurrentChannelUri(@NonNull String);
     method public void onRequestCurrentTvInputId(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
     method public void onRequestStreamVolume(@NonNull String);
     method public void onRequestTrackInfoList(@NonNull String);
     method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
@@ -41783,7 +41787,8 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
     field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
-    field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array";
+    field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int";
+    field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_monthly_notification_count_int";
     field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
@@ -44051,7 +44056,6 @@
     field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
     field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
     field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
-    field public static final long NETWORK_TYPE_BITMASK_IDEN = 1024L; // 0x400L
     field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
     field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
     field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
@@ -44071,7 +44075,7 @@
     field public static final int NETWORK_TYPE_HSPA = 10; // 0xa
     field public static final int NETWORK_TYPE_HSPAP = 15; // 0xf
     field public static final int NETWORK_TYPE_HSUPA = 9; // 0x9
-    field public static final int NETWORK_TYPE_IDEN = 11; // 0xb
+    field @Deprecated public static final int NETWORK_TYPE_IDEN = 11; // 0xb
     field public static final int NETWORK_TYPE_IWLAN = 18; // 0x12
     field public static final int NETWORK_TYPE_LTE = 13; // 0xd
     field public static final int NETWORK_TYPE_NR = 20; // 0x14
@@ -44090,7 +44094,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
-    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14; // 0xe
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
@@ -50112,7 +50116,7 @@
     method public void dispatchCreateViewTranslationRequest(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @NonNull android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
     method public void dispatchDisplayHint(int);
     method public boolean dispatchDragEvent(android.view.DragEvent);
-    method protected void dispatchDraw(android.graphics.Canvas);
+    method protected void dispatchDraw(@NonNull android.graphics.Canvas);
     method public void dispatchDrawableHotspotChanged(float, float);
     method @CallSuper public void dispatchFinishTemporaryDetach();
     method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
@@ -50150,7 +50154,7 @@
     method @NonNull public android.view.WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimation, @NonNull android.view.WindowInsetsAnimation.Bounds);
     method @Deprecated public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
-    method @CallSuper public void draw(android.graphics.Canvas);
+    method @CallSuper public void draw(@NonNull android.graphics.Canvas);
     method @CallSuper public void drawableHotspotChanged(float, float);
     method @CallSuper protected void drawableStateChanged();
     method public android.view.View findFocus();
@@ -50443,9 +50447,9 @@
     method @CallSuper protected void onDetachedFromWindow();
     method protected void onDisplayHint(int);
     method public boolean onDragEvent(android.view.DragEvent);
-    method protected void onDraw(android.graphics.Canvas);
-    method public void onDrawForeground(android.graphics.Canvas);
-    method protected final void onDrawScrollBars(android.graphics.Canvas);
+    method protected void onDraw(@NonNull android.graphics.Canvas);
+    method public void onDrawForeground(@NonNull android.graphics.Canvas);
+    method protected final void onDrawScrollBars(@NonNull android.graphics.Canvas);
     method public boolean onFilterTouchEventForSecurity(android.view.MotionEvent);
     method @CallSuper protected void onFinishInflate();
     method public void onFinishTemporaryDetach();
@@ -50931,7 +50935,7 @@
     ctor public View.DragShadowBuilder(android.view.View);
     ctor public View.DragShadowBuilder();
     method public final android.view.View getView();
-    method public void onDrawShadow(android.graphics.Canvas);
+    method public void onDrawShadow(@NonNull android.graphics.Canvas);
     method public void onProvideShadowMetrics(android.graphics.Point, android.graphics.Point);
   }
 
@@ -51161,7 +51165,7 @@
     method public void dispatchSetActivated(boolean);
     method public void dispatchSetSelected(boolean);
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
-    method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
+    method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
@@ -52334,6 +52338,7 @@
     method public CharSequence getClassName();
     method public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo getCollectionInfo();
     method public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo getCollectionItemInfo();
+    method @Nullable public CharSequence getContainerTitle();
     method public CharSequence getContentDescription();
     method public int getDrawingOrder();
     method public CharSequence getError();
@@ -52410,6 +52415,7 @@
     method public void setClickable(boolean);
     method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
     method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
+    method public void setContainerTitle(@Nullable CharSequence);
     method public void setContentDescription(CharSequence);
     method public void setContentInvalid(boolean);
     method public void setContextClickable(boolean);
@@ -53398,7 +53404,7 @@
     method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setTextAppearanceInfo(@Nullable android.view.inputmethod.TextAppearanceInfo);
   }
 
-  public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class DeleteGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.RectF getDeletionArea();
     method public int getGranularity();
@@ -53414,7 +53420,7 @@
     method @NonNull public android.view.inputmethod.DeleteGesture.Builder setGranularity(int);
   }
 
-  public final class DeleteRangeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class DeleteRangeGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.RectF getDeletionEndArea();
     method @NonNull public android.graphics.RectF getDeletionStartArea();
@@ -53456,11 +53462,13 @@
     method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getInitialTextBeforeCursor(@IntRange(from=0) int, int);
     method public int getInitialToolType();
+    method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews();
     method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
     method public final void makeCompatible(int);
     method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
     method public void setInitialSurroundingText(@NonNull CharSequence);
     method public void setInitialToolType(int);
+    method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>);
     method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
@@ -53635,6 +53643,7 @@
     method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
     method public boolean performPrivateCommand(String, android.os.Bundle);
     method public default boolean performSpellCheck();
+    method public default boolean previewHandwritingGesture(@NonNull android.view.inputmethod.PreviewableHandwritingGesture, @Nullable android.os.CancellationSignal);
     method public default boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
@@ -53895,6 +53904,9 @@
     method @NonNull public android.view.inputmethod.JoinOrSplitGesture.Builder setJoinOrSplitPoint(@NonNull android.graphics.PointF);
   }
 
+  public abstract class PreviewableHandwritingGesture extends android.view.inputmethod.HandwritingGesture {
+  }
+
   public final class RemoveSpaceGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.graphics.PointF getEndPoint();
@@ -53910,7 +53922,7 @@
     method @NonNull public android.view.inputmethod.RemoveSpaceGesture.Builder setPoints(@NonNull android.graphics.PointF, @NonNull android.graphics.PointF);
   }
 
-  public final class SelectGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class SelectGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method public int getGranularity();
     method @NonNull public android.graphics.RectF getSelectionArea();
@@ -53926,7 +53938,7 @@
     method @NonNull public android.view.inputmethod.SelectGesture.Builder setSelectionArea(@NonNull android.graphics.RectF);
   }
 
-  public final class SelectRangeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+  public final class SelectRangeGesture extends android.view.inputmethod.PreviewableHandwritingGesture implements android.os.Parcelable {
     method public int describeContents();
     method public int getGranularity();
     method @NonNull public android.graphics.RectF getSelectionEndArea();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 70b89b8..ae6e58c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3617,6 +3617,7 @@
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
     field @Deprecated public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
     field @Nullable public final String backgroundPermission;
+    field @NonNull public java.util.Set<java.lang.String> knownCerts;
     field @StringRes public int requestRes;
   }
 
@@ -5555,6 +5556,28 @@
     method @NonNull public android.location.CorrelationVector.Builder setSamplingWidthMeters(@FloatRange(from=0.0f, fromInclusive=false) double);
   }
 
+  public final class Country implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getCountryIso();
+    method public int getSource();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int COUNTRY_SOURCE_LOCALE = 3; // 0x3
+    field public static final int COUNTRY_SOURCE_LOCATION = 1; // 0x1
+    field public static final int COUNTRY_SOURCE_NETWORK = 0; // 0x0
+    field public static final int COUNTRY_SOURCE_SIM = 2; // 0x2
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.Country> CREATOR;
+  }
+
+  public class CountryDetector {
+    method public void addCountryListener(@NonNull android.location.CountryListener, @Nullable android.os.Looper);
+    method @Nullable public android.location.Country detectCountry();
+    method public void removeCountryListener(@NonNull android.location.CountryListener);
+  }
+
+  public interface CountryListener {
+    method public void onCountryDetected(@NonNull android.location.Country);
+  }
+
   public final class GnssCapabilities implements android.os.Parcelable {
     method @Deprecated public boolean hasMeasurementCorrectionsReflectingPane();
     method @Deprecated public boolean hasNavMessages();
@@ -13125,6 +13148,7 @@
     field public static final int PRECISE_CALL_STATE_HOLDING = 2; // 0x2
     field public static final int PRECISE_CALL_STATE_IDLE = 0; // 0x0
     field public static final int PRECISE_CALL_STATE_INCOMING = 5; // 0x5
+    field public static final int PRECISE_CALL_STATE_INCOMING_SETUP = 9; // 0x9
     field public static final int PRECISE_CALL_STATE_NOT_VALID = -1; // 0xffffffff
     field public static final int PRECISE_CALL_STATE_WAITING = 6; // 0x6
   }
@@ -14213,7 +14237,8 @@
     field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd
     field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe
     field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
+    field public static final int RESULT_PROFILE_DOES_NOT_EXIST = -4; // 0xfffffffc
+    field @Deprecated public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1
     field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
   }
 
@@ -15380,6 +15405,16 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipMessage> CREATOR;
   }
 
+  public final class SrvccCall implements android.os.Parcelable {
+    ctor public SrvccCall(@NonNull String, int, @NonNull android.telephony.ims.ImsCallProfile);
+    method public int describeContents();
+    method @NonNull public String getCallId();
+    method @NonNull public android.telephony.ims.ImsCallProfile getImsCallProfile();
+    method public int getPreciseCallState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SrvccCall> CREATOR;
+  }
+
 }
 
 package android.telephony.ims.feature {
@@ -15440,6 +15475,10 @@
     method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
     method public final void notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull android.os.Bundle);
     method public final void notifyRejectedCall(@NonNull android.telephony.ims.ImsCallProfile, @NonNull android.telephony.ims.ImsReasonInfo);
+    method public void notifySrvccCanceled();
+    method public void notifySrvccCompleted();
+    method public void notifySrvccFailed();
+    method public void notifySrvccStarted(@NonNull java.util.function.Consumer<java.util.List<android.telephony.ims.SrvccCall>>);
     method public final void notifyVoiceMessageCountUpdate(int);
     method public void onFeatureReady();
     method public void onFeatureRemoved();
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 025e862..47588d9 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -3,6 +3,10 @@
     Method should return Collection<CharSequence> (or subclass) instead of raw array; was `java.lang.CharSequence[]`
 
 
+ExecutorRegistration: android.location.CountryDetector#addCountryListener(android.location.CountryListener, android.os.Looper):
+    Registration methods should have overload that accepts delivery Executor: `addCountryListener`
+
+
 GenericException: android.app.prediction.AppPredictor#finalize():
     Methods must not throw generic exceptions (`java.lang.Throwable`)
 GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -127,6 +131,8 @@
     SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ResolveInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.CountryDetector#addCountryListener(android.location.CountryListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.location.CountryDetector.addCountryListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.location.Location.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e9f9136..f076395 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1252,6 +1252,7 @@
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
+    field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
     field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
     field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
   }
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 89601bc..45515dd 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -117,27 +117,27 @@
     /**
      * Bundle key used for the {@link String} account type in session bundle.
      * This is used in the default implementation of
-     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_AUTH_TOKEN_TYPE =
             "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
     /**
      * Bundle key used for the {@link String} array of required features in
      * session bundle. This is used in the default implementation of
-     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_REQUIRED_FEATURES =
             "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
     /**
      * Bundle key used for the {@link Bundle} options in session bundle. This is
      * used in default implementation of {@link #startAddAccountSession} and
-     * {@link startUpdateCredentialsSession}.
+     * {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_OPTIONS =
             "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
     /**
      * Bundle key used for the {@link Account} account in session bundle. This is used
-     * used in default implementation of {@link startUpdateCredentialsSession}.
+     * used in default implementation of {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_ACCOUNT =
             "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index baa3bd9..dc7331f 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -649,6 +649,8 @@
      * using the rules of package visibility. Returns extras with legitimate package info that the
      * receiver is able to access, or {@code null} if none of the packages is visible to the
      * receiver.
+     * @param serialized Specifies whether or not the broadcast should be delivered to the
+     *                   receivers in a serial order.
      *
      * @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
      *      IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
@@ -662,6 +664,19 @@
             @Nullable Bundle bOptions);
 
     /**
+     * Variant of
+     * {@link #broadcastIntent(Intent, IIntentReceiver, String[], boolean, int, int[], BiFunction, Bundle)}
+     * that allows sender to receive a finish callback once the broadcast delivery is completed,
+     * but provides no ordering guarantee for how the broadcast is delivered to receivers.
+     */
+    public abstract int broadcastIntentWithCallback(Intent intent,
+            IIntentReceiver resultTo,
+            String[] requiredPermissions,
+            int userId, int[] appIdAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @Nullable Bundle bOptions);
+
+    /**
      * Add uid to the ActivityManagerService PendingStartActivityUids list.
      * @param uid uid
      * @param pid pid of the ProcessRecord that is pending top.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 267e5b6..dc325ff 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1368,9 +1368,31 @@
     public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
             AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
 
+    /**
+     * Prevent an app from being placed into app standby buckets.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+
+    /**
+     * Prevent an app from being placed into forced app standby.
+     * {@link ActivityManager#isBackgroundRestricted()}
+     * {@link #OP_RUN_ANY_IN_BACKGROUND}
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 124;
+    public static final int _NUM_OP = 126;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1869,6 +1891,28 @@
      */
     public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
 
+    /**
+     * Prevent an app from being placed into app standby buckets.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+            "android:system_exempt_from_app_standby";
+
+    /**
+     * Prevent an app from being placed into forced app standby.
+     * {@link ActivityManager#isBackgroundRestricted()}
+     * {@link #OP_RUN_ANY_IN_BACKGROUND}
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+            "android:system_exempt_from_forced_app_standby";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2350,7 +2394,13 @@
             new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
                     OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
                     .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
-                    .setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
+                    .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                "SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+                OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+                "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
     };
 
     /**
diff --git a/core/java/android/app/ILocaleManager.aidl b/core/java/android/app/ILocaleManager.aidl
index 3002c8b..c38b64f 100644
--- a/core/java/android/app/ILocaleManager.aidl
+++ b/core/java/android/app/ILocaleManager.aidl
@@ -33,7 +33,7 @@
      /**
       * Sets a specified app’s app-specific UI locales.
       */
-     void setApplicationLocales(String packageName, int userId, in LocaleList locales);
+     void setApplicationLocales(String packageName, int userId, in LocaleList locales, boolean fromDelegate);
 
      /**
       * Returns the specified app's app-specific locales.
@@ -45,4 +45,4 @@
        */
      LocaleList getSystemLocales();
 
- }
\ No newline at end of file
+ }
diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java
index 0e2b098..70c014f 100644
--- a/core/java/android/app/LocaleManager.java
+++ b/core/java/android/app/LocaleManager.java
@@ -71,7 +71,7 @@
      */
     @UserHandleAware
     public void setApplicationLocales(@NonNull LocaleList locales) {
-        setApplicationLocales(mContext.getPackageName(), locales);
+        setApplicationLocales(mContext.getPackageName(), locales, false);
     }
 
     /**
@@ -100,9 +100,14 @@
     @RequiresPermission(Manifest.permission.CHANGE_CONFIGURATION)
     @UserHandleAware
     public void setApplicationLocales(@NonNull String appPackageName, @NonNull LocaleList locales) {
+        setApplicationLocales(appPackageName, locales, true);
+    }
+
+    private void setApplicationLocales(@NonNull String appPackageName, @NonNull LocaleList locales,
+            boolean fromDelegate) {
         try {
             mService.setApplicationLocales(appPackageName, mContext.getUser().getIdentifier(),
-                    locales);
+                    locales, fromDelegate);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f3fc468..2012948 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -11,6 +11,7 @@
 per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS
+per-file ForegroundService* = file:/services/core/java/com/android/server/am/OWNERS
 per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS
 per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index 3f1844e..96d874e 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -157,13 +157,18 @@
         }
 
         /**
-         * Sets the source bounds hint. These bounds are only used when an activity first enters
-         * picture-in-picture, and describe the bounds in window coordinates of activity entering
-         * picture-in-picture that will be visible following the transition. For the best effect,
-         * these bounds should also match the aspect ratio in the arguments.
+         * Sets the window-coordinate bounds of an activity transitioning to picture-in-picture.
+         * The bounds is the area of an activity that will be visible in the transition to
+         * picture-in-picture mode. For the best effect, these bounds should also match the
+         * aspect ratio in the arguments.
+         *
+         * In Android 12+ these bounds are also reused to improve the exit transition from 
+         * picture-in-picture mode. See
+         * <a href="{@docRoot}develop/ui/views/picture-in-picture#smoother-exit">Support
+         * smoother animations when exiting out of PiP mode</a> for more details.
          *
          * @param launchBounds window-coordinate bounds indicating the area of the activity that
-         * will still be visible following the transition into picture-in-picture (eg. the video
+         * will still be visible following the transition into picture-in-picture (e.g. the video
          * view bounds in a video player)
          *
          * @return this builder instance.
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b0c6cbc..e981581 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.UserHandle;
 import android.permission.PermissionManager;
 import android.util.ArraySet;
 
@@ -297,7 +298,7 @@
     public boolean checkCallingUid() {
         final int callingUid = Binder.getCallingUid();
         if (callingUid != Process.ROOT_UID
-                && callingUid != Process.SYSTEM_UID
+                && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID
                 && callingUid != mAttributionSourceState.uid) {
             return false;
         }
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index bb88486..7c22c088 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -33,6 +34,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -486,7 +488,10 @@
      *
      * @hide
      */
-    public @Nullable Set<String> knownCerts;
+    // Already being used as mutable and most other fields in this class are also mutable.
+    @SuppressLint("MutableBareField")
+    @SystemApi
+    public @NonNull Set<String> knownCerts = Collections.emptySet();
 
     /** @hide */
     public static int fixProtectionLevel(int level) {
@@ -620,6 +625,8 @@
         descriptionRes = orig.descriptionRes;
         requestRes = orig.requestRes;
         nonLocalizedDescription = orig.nonLocalizedDescription;
+        // Note that knownCerts wasn't properly copied before Android U.
+        knownCerts = orig.knownCerts;
     }
 
     /**
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 645a1ac..80f3264 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -43,17 +43,20 @@
     // Attribute strings for reading/writing properties to/from XML.
     private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
+    private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
             INDEX_SHOW_IN_LAUNCHER,
             INDEX_START_WITH_PARENT,
+            INDEX_SHOW_IN_SETTINGS,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
     }
     private static final int INDEX_SHOW_IN_LAUNCHER = 0;
     private static final int INDEX_START_WITH_PARENT = 1;
+    private static final int INDEX_SHOW_IN_SETTINGS = 2;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -87,6 +90,37 @@
     public static final int SHOW_IN_LAUNCHER_NO = 2;
 
     /**
+     * Possible values for whether or how to show this user in the Settings app.
+     * @hide
+     */
+    @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+            SHOW_IN_SETTINGS_WITH_PARENT,
+            SHOW_IN_SETTINGS_SEPARATE,
+            SHOW_IN_SETTINGS_NO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowInSettings {
+    }
+    /**
+     * Suggests that the Settings app should show this user's apps in the main tab.
+     * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+     * this user is a profile, then its apps should be shown alongside its parent's apps.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_WITH_PARENT = 0;
+    /**
+     * Suggests that the Settings app should show this user's apps, but separately from the apps of
+     * this user's parent.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_SEPARATE = 1;
+    /**
+     * Suggests that the Settings app should not show this user.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_NO = 2;
+
+    /**
      * Reference to the default user properties for this user's user type.
      * <li>If non-null, then any absent property will use the default property from here instead.
      * <li>If null, then any absent property indicates that the caller lacks permission to see it,
@@ -130,6 +164,7 @@
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
+            setShowInSettings(orig.getShowInSettings());
         }
         if (hasQueryOrManagePermission) {
             // Add items that require QUERY_USERS or stronger.
@@ -183,6 +218,33 @@
     private @ShowInLauncher int mShowInLauncher;
 
     /**
+     * Returns whether, and how, a user should be shown in the Settings app.
+     * This is generally inapplicable for non-profile users.
+     *
+     * Possible return values include
+     *    {@link #SHOW_IN_SETTINGS_WITH_PARENT}},
+     *    {@link #SHOW_IN_SETTINGS_SEPARATE},
+     *    and {@link #SHOW_IN_SETTINGS_NO}.
+     *
+     * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+     * property.
+     *
+     * @return whether, and how, a profile should be shown in the Settings.
+     * @hide
+     */
+    public @ShowInSettings int getShowInSettings() {
+        if (isPresent(INDEX_SHOW_IN_SETTINGS)) return mShowInSettings;
+        if (mDefaultProperties != null) return mDefaultProperties.mShowInSettings;
+        throw new SecurityException("You don't have permission to query mShowInSettings");
+    }
+    /** @hide */
+    public void setShowInSettings(@ShowInSettings int val) {
+        this.mShowInSettings = val;
+        setPresent(INDEX_SHOW_IN_SETTINGS);
+    }
+    private @ShowInSettings int mShowInSettings;
+
+    /**
      * Returns whether a profile should be started when its parent starts (unless in quiet mode).
      * This only applies for users that have parents (i.e. for profiles).
      * @hide
@@ -206,6 +268,7 @@
                 + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
                 + ", mShowInLauncher=" + getShowInLauncher()
                 + ", mStartWithParent=" + getStartWithParent()
+                + ", mShowInSettings=" + getShowInSettings()
                 + "}";
     }
 
@@ -219,6 +282,7 @@
         pw.println(prefix + "    mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
         pw.println(prefix + "    mShowInLauncher=" + getShowInLauncher());
         pw.println(prefix + "    mStartWithParent=" + getStartWithParent());
+        pw.println(prefix + "    mShowInSettings=" + getShowInSettings());
     }
 
     /**
@@ -258,6 +322,8 @@
                 case ATTR_START_WITH_PARENT:
                     setStartWithParent(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_SHOW_IN_SETTINGS:
+                    setShowInSettings(parser.getAttributeInt(i));
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -281,6 +347,9 @@
         if (isPresent(INDEX_START_WITH_PARENT)) {
             serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
         }
+        if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
+            serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -289,6 +358,7 @@
         dest.writeLong(mPropertiesPresent);
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
+        dest.writeInt(mShowInSettings);
     }
 
     /**
@@ -301,6 +371,7 @@
         mPropertiesPresent = source.readLong();
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
+        mShowInSettings = source.readInt();
     }
 
     @Override
@@ -327,6 +398,7 @@
         // UserProperties fields and their default values.
         private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
         private boolean mStartWithParent = false;
+        private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -338,21 +410,30 @@
             return this;
         }
 
+        /** Sets the value for {@link #mShowInSettings} */
+        public Builder setShowInSettings(@ShowInSettings int showInSettings) {
+            mShowInSettings = showInSettings;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
                     mShowInLauncher,
-                    mStartWithParent);
+                    mStartWithParent,
+                    mShowInSettings);
         }
     } // end Builder
 
     /** Creates a UserProperties with the given properties. Intended for building default values. */
     private UserProperties(
             @ShowInLauncher int showInLauncher,
-            boolean startWithParent) {
+            boolean startWithParent,
+            @ShowInSettings int showInSettings) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
+        setShowInSettings(showInSettings);
     }
 }
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 30ee118..c9a0626 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -62,10 +62,10 @@
      * <p>The execution can potentially launch UI flows to collect user consent to using a
      * credential, display a picker when multiple credentials exist, etc.
      *
-     * @param request the request specifying type(s) of credentials to get from the user.
-     * @param cancellationSignal an optional signal that allows for cancelling this call.
-     * @param executor the callback will take place on this {@link Executor}.
-     * @param callback the callback invoked when the request succeeds or fails.
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeGetCredential(
             @NonNull GetCredentialRequest request,
@@ -101,10 +101,10 @@
      * <p>The execution can potentially launch UI flows to collect user consent to creating
      * or storing the new credential, etc.
      *
-     * @param request the request specifying type(s) of credentials to get from the user.
-     * @param cancellationSignal an optional signal that allows for cancelling this call.
-     * @param executor the callback will take place on this {@link Executor}.
-     * @param callback the callback invoked when the request succeeds or fails.
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeCreateCredential(
             @NonNull CreateCredentialRequest request,
@@ -135,6 +135,44 @@
         }
     }
 
+    /**
+     * Clears the current user credential session from all credential providers.
+     *
+     * <p>Usually invoked after your user signs out of your app so that they will not be
+     * automatically signed in the next time.
+     *
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     *
+     * @hide
+     */
+    public void clearCredentialSession(
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeCreateCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.clearCredentialSession(
+                    new ClearCredentialSessionTransport(executor, callback),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
@@ -184,4 +222,29 @@
                     () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
         }
     }
+
+    private static class ClearCredentialSessionTransport
+            extends IClearCredentialSessionCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+
+        private ClearCredentialSessionTransport(Executor executor,
+                OutcomeReceiver<Void, CredentialManagerException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSuccess() {
+            mCallback.onResult(null);
+        }
+
+        @Override
+        public void onError(int errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
similarity index 66%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to core/java/android/credentials/IClearCredentialSessionCallback.aidl
index f79ca10..903e7f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
-
-import android.content.Intent;
+package android.credentials;
 
 /**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Listener for clearCredentialSession request.
+ *
+ * @hide
  */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
-}
+interface IClearCredentialSessionCallback {
+    oneway void onSuccess();
+    oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index b0f27f9..35688d7 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,6 +18,7 @@
 
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
 import android.os.ICancellationSignal;
@@ -32,4 +33,6 @@
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+
+    @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
 }
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 9cc9c72..98157d7 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -34,19 +34,15 @@
 public class CreateCredentialProviderData extends ProviderData implements Parcelable {
     @NonNull
     private final List<Entry> mSaveEntries;
-    @NonNull
-    private final List<Entry> mActionChips;
     private final boolean mIsDefaultProvider;
     @Nullable
     private final Entry mRemoteEntry;
 
     public CreateCredentialProviderData(
             @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
-            @NonNull List<Entry> actionChips, boolean isDefaultProvider,
-            @Nullable Entry remoteEntry) {
+            boolean isDefaultProvider, @Nullable Entry remoteEntry) {
         super(providerFlattenedComponentName);
         mSaveEntries = saveEntries;
-        mActionChips = actionChips;
         mIsDefaultProvider = isDefaultProvider;
         mRemoteEntry = remoteEntry;
     }
@@ -56,11 +52,6 @@
         return mSaveEntries;
     }
 
-    @NonNull
-    public List<Entry> getActionChips() {
-        return mActionChips;
-    }
-
     public boolean isDefaultProvider() {
         return mIsDefaultProvider;
     }
@@ -78,11 +69,6 @@
         mSaveEntries = credentialEntries;
         AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
 
-        List<Entry> actionChips  = new ArrayList<>();
-        in.readTypedList(actionChips, Entry.CREATOR);
-        mActionChips = actionChips;
-        AnnotationValidations.validate(NonNull.class, null, mActionChips);
-
         mIsDefaultProvider = in.readBoolean();
 
         Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -93,7 +79,6 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeTypedList(mSaveEntries);
-        dest.writeTypedList(mActionChips);
         dest.writeBoolean(isDefaultProvider());
         dest.writeTypedObject(mRemoteEntry, flags);
     }
@@ -124,7 +109,6 @@
     public static class Builder {
         private @NonNull String mProviderFlattenedComponentName;
         private @NonNull List<Entry> mSaveEntries = new ArrayList<>();
-        private @NonNull List<Entry> mActionChips = new ArrayList<>();
         private boolean mIsDefaultProvider = false;
         private @Nullable Entry mRemoteEntry = null;
 
@@ -140,13 +124,6 @@
             return this;
         }
 
-        /** Sets the list of action chips to be displayed to the user. */
-        @NonNull
-        public Builder setActionChips(@NonNull List<Entry> actionChips) {
-            mActionChips = actionChips;
-            return this;
-        }
-
         /** Sets whether this provider is the user's selected default provider. */
         @NonNull
         public Builder setIsDefaultProvider(boolean isDefaultProvider) {
@@ -158,7 +135,7 @@
         @NonNull
         public CreateCredentialProviderData build() {
             return new CreateCredentialProviderData(mProviderFlattenedComponentName,
-                    mSaveEntries, mActionChips, mIsDefaultProvider, mRemoteEntry);
+                    mSaveEntries, mIsDefaultProvider, mRemoteEntry);
         }
     }
 }
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 33427d3..6fa331b 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -53,7 +53,8 @@
     /** Below are only available for get flows. */
     public static final String HINT_NOTE = "HINT_NOTE";
     public static final String HINT_USER_NAME = "HINT_USER_NAME";
-    public static final String HINT_CREDENTIAL_TYPE = "HINT_CREDENTIAL_TYPE";
+    public static final String HINT_CREDENTIAL_TYPE_DISPLAY_NAME =
+            "HINT_CREDENTIAL_TYPE_DISPLAY_NAME";
     public static final String HINT_PASSKEY_USER_DISPLAY_NAME = "HINT_PASSKEY_USER_DISPLAY_NAME";
     public static final String HINT_PASSWORD_VALUE = "HINT_PASSWORD_VALUE";
 
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 59d5118..c3937b6 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -69,25 +69,24 @@
 
     private final boolean mIsFirstUsage;
 
-    // TODO: change to package name
     @NonNull
-    private final String mAppDisplayName;
+    private final String mAppPackageName;
 
     /** Creates new {@code RequestInfo} for a create-credential flow. */
     public static RequestInfo newCreateRequestInfo(
             @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            boolean isFirstUsage, @NonNull String appDisplayName) {
+            boolean isFirstUsage, @NonNull String appPackageName) {
         return new RequestInfo(
-                token, TYPE_CREATE, isFirstUsage, appDisplayName,
+                token, TYPE_CREATE, isFirstUsage, appPackageName,
                 createCredentialRequest, null);
     }
 
     /** Creates new {@code RequestInfo} for a get-credential flow. */
     public static RequestInfo newGetRequestInfo(
             @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            boolean isFirstUsage, @NonNull String appDisplayName) {
+            boolean isFirstUsage, @NonNull String appPackageName) {
         return new RequestInfo(
-                token, TYPE_GET, isFirstUsage, appDisplayName,
+                token, TYPE_GET, isFirstUsage, appPackageName,
                 null, getCredentialRequest);
     }
 
@@ -116,8 +115,8 @@
 
     /** Returns the display name of the app that made this request. */
     @NonNull
-    public String getAppDisplayName() {
-        return mAppDisplayName;
+    public String getAppPackageName() {
+        return mAppPackageName;
     }
 
     /**
@@ -139,13 +138,13 @@
     }
 
     private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
-            boolean isFirstUsage, @NonNull String appDisplayName,
+            boolean isFirstUsage, @NonNull String appPackageName,
             @Nullable CreateCredentialRequest createCredentialRequest,
             @Nullable GetCredentialRequest getCredentialRequest) {
         mToken = token;
         mType = type;
         mIsFirstUsage = isFirstUsage;
-        mAppDisplayName = appDisplayName;
+        mAppPackageName = appPackageName;
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
     }
@@ -154,7 +153,7 @@
         IBinder token = in.readStrongBinder();
         String type = in.readString8();
         boolean isFirstUsage = in.readBoolean();
-        String appDisplayName = in.readString8();
+        String appPackageName = in.readString8();
         CreateCredentialRequest createCredentialRequest =
                 in.readTypedObject(CreateCredentialRequest.CREATOR);
         GetCredentialRequest getCredentialRequest =
@@ -165,8 +164,8 @@
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
         mIsFirstUsage = isFirstUsage;
-        mAppDisplayName = appDisplayName;
-        AnnotationValidations.validate(NonNull.class, null, mAppDisplayName);
+        mAppPackageName = appPackageName;
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
     }
@@ -176,7 +175,7 @@
         dest.writeStrongBinder(mToken);
         dest.writeString8(mType);
         dest.writeBoolean(mIsFirstUsage);
-        dest.writeString8(mAppDisplayName);
+        dest.writeString8(mAppPackageName);
         dest.writeTypedObject(mCreateCredentialRequest, flags);
         dest.writeTypedObject(mGetCredentialRequest, flags);
     }
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index 24480e9..beb7f36 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -198,6 +198,15 @@
      */
     public static final int RESULT_ERROR_INVALID_XML = -10007;
 
+    /**
+     * Indicates a failure due to invalid debug certificate file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_INVALID_DEBUG_CERTIFICATE = -10008;
+
     private FontManager(@NonNull IFontManager iFontManager) {
         mIFontManager = iFontManager;
     }
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 99b58c9..535b551 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -73,6 +73,13 @@
             + ".extra.all_sensors";
 
     /**
+     * An extra containing the sensor type
+     * @hide
+     */
+    public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName()
+            + ".extra.toggle_type";
+
+    /**
      * Sensor constants which are used in {@link SensorPrivacyManager}
      */
     public static class Sensors {
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 2eb8cb9..6b044fc 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -639,5 +639,24 @@
             }
         }
     }
+
+    /**
+     * Notifies AuthService that keyguard has been dismissed for the given userId.
+     *
+     * @param userId
+     * @param hardwareAuthToken
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void resetLockout(int userId, byte[] hardwareAuthToken) {
+        if (mService != null) {
+            try {
+                mService.resetLockout(userId, hardwareAuthToken);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricStateListener.java b/core/java/android/hardware/biometrics/BiometricStateListener.java
index b167cc6..71b7850 100644
--- a/core/java/android/hardware/biometrics/BiometricStateListener.java
+++ b/core/java/android/hardware/biometrics/BiometricStateListener.java
@@ -73,4 +73,5 @@
      */
     public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
     }
+
 }
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 7c3cc10b..c2e5c0b 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -79,6 +79,9 @@
     void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId,
             in byte[] hardwareAuthToken);
 
+    // See documentation in BiometricManager.
+    void resetLockout(int userId, in byte[] hardwareAuthToken);
+
     // Provides a localized string that may be used as the label for a button that invokes
     // BiometricPrompt.
     CharSequence getButtonLabel(int userId, String opPackageName, int authenticators);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 08f9ed6..c88af5a 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -91,6 +91,10 @@
     void resetLockoutTimeBound(IBinder token, String opPackageName, int fromSensorId, int userId,
             in byte[] hardwareAuthToken);
 
+    // See documentation in BiometricManager.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void resetLockout(int userId, in byte[] hardwareAuthToken);
+
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     int getCurrentStrength(int sensorId);
 
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index b9a327b..d9ee561 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -204,7 +204,7 @@
          *
          */
         @SuppressLint("MissingGetterMatchingBuilder")
-        public @NonNull Builder addOrientationForState(long deviceState, long angle) {
+        public @NonNull Builder addOrientationForState(@DeviceState long deviceState, long angle) {
             if (angle % 90 != 0) {
                 throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
                         + angle);
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index 1d9a5a3a..32688a72 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -17,6 +17,7 @@
 
 package android.hardware.camera2.params;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Point;
@@ -173,6 +174,7 @@
      * @see #SCORE_MAX
      * @see #SCORE_MIN
      */
+    @IntRange(from = SCORE_MIN, to = SCORE_MAX)
     public int getScore() {
         return mScore;
     }
@@ -377,7 +379,7 @@
          * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
          * @return This builder.
          */
-        public @NonNull Builder setScore(int score) {
+        public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) {
             checkNotUsed();
             checkScore(score);
             mBuilderFieldsSet |= FIELD_SCORE;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8311190..9b07d3a 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -439,12 +439,13 @@
             SWITCHING_TYPE_NONE,
             SWITCHING_TYPE_WITHIN_GROUPS,
             SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS,
+            SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SwitchingType {}
 
     /**
-     * No mode switching will happen.
+     * No display mode switching will happen.
      * @hide
      */
     @TestApi
@@ -467,6 +468,13 @@
     public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2;
 
     /**
+     * Allow render frame rate switches, but not physical modes.
+     * @hide
+     */
+    @TestApi
+    public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3;
+
+    /**
      * @hide
      */
     @LongDef(flag = true, prefix = {"EVENT_FLAG_"}, value = {
@@ -1308,6 +1316,7 @@
         switch (switchingType) {
             case SWITCHING_TYPE_NONE:
                 return MATCH_CONTENT_FRAMERATE_NEVER;
+            case SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY:
             case SWITCHING_TYPE_WITHIN_GROUPS:
                 return MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY;
             case SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS:
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8b71092..59465db 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -251,7 +251,8 @@
                     Objects.requireNonNull(entry.getValue());
                 }
             }
-            mDabFrequencyTable = dabFrequencyTable;
+            mDabFrequencyTable = (dabFrequencyTable == null || dabFrequencyTable.isEmpty())
+                    ? null : dabFrequencyTable;
             mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
         }
 
@@ -446,7 +447,8 @@
             mIsBgScanSupported = in.readInt() == 1;
             mSupportedProgramTypes = arrayToSet(in.createIntArray());
             mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
-            mDabFrequencyTable = Utils.readStringIntMap(in);
+            Map<String, Integer> dabFrequencyTableIn = Utils.readStringIntMap(in);
+            mDabFrequencyTable = (dabFrequencyTableIn.isEmpty()) ? null : dabFrequencyTableIn;
             mVendorInfo = Utils.readStringMap(in);
         }
 
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 95aecfe..c6a3725 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
@@ -697,6 +698,27 @@
     }
 
     /**
+     * Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture(
+     * InputConnectionCommandHeader, ParcelableHandwritingGesture, CancellationSignal)}
+     */
+    @AnyThread
+    public boolean previewHandwritingGesture(
+            @NonNull ParcelableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            return false; // cancelled.
+        }
+
+        // TODO(b/254727073): Implement CancellationSignal
+        try {
+            mConnection.previewHandwritingGesture(createHeader(), gesture, null);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
      * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
      * int, AndroidFuture)}.
      *
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 39d362b..d902486 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1774,16 +1774,13 @@
     }
 
     /**
-     * Implement to return our standard {@link InputMethodImpl}.  Subclasses
-     * can override to provide their own customized version.
+     * Implement to return our standard {@link InputMethodImpl}.
      *
-     * @deprecated IME developers don't need to override this method to get callbacks information.
-     * Most methods in {@link InputMethodImpl} have corresponding callbacks.
-     * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()},
-     * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc.
-     *
-     * <p>Starting from Android U and later, override this method won't guarantee that IME works
-     * as previous platform behavior.</p>
+     * @deprecated Overriding or calling this method is strongly discouraged. A future version of
+     * Android will remove the ability to use this method. Use the callbacks on
+     * {@link InputMethodService} as {@link InputMethodService#onBindInput()},
+     * {@link InputMethodService#onUnbindInput()}, {@link InputMethodService#onWindowShown()},
+     * {@link InputMethodService#onWindowHidden()}, etc.
      */
     @Deprecated
     @Override
@@ -1792,18 +1789,17 @@
     }
     
     /**
-     * Implement to return our standard {@link InputMethodSessionImpl}.  Subclasses
-     * can override to provide their own customized version.
+     * Implement to return our standard {@link InputMethodSessionImpl}.
      *
-     * @deprecated IME developers don't need to override this method to get callbacks information.
+     * <p>IMEs targeting on Android U and above cannot override this method, or an
+     * {@link LinkageError} would be thrown.</p>
+     *
+     * @deprecated Overriding or calling this method is strongly discouraged.
      * Most methods in {@link InputMethodSessionImpl} have corresponding callbacks.
      * Use {@link InputMethodService#onFinishInput()},
      * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
      * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
      * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
-     *
-     * <p>IMEs targeting on Android U and above cannot override this method, or an
-     * {@link LinkageError} would be thrown.</p>
      */
     @Deprecated
     @Override
@@ -2272,6 +2268,8 @@
      * current input field.
      * 
      * @param id Unique identifier of the new input method to start.
+     * @throws IllegalArgumentException if the input method is unknown or filtered
+     * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
      */
     public void switchInputMethod(String id) {
         mPrivOps.setInputMethod(id);
@@ -2284,6 +2282,8 @@
      *
      * @param id Unique identifier of the new input method to start.
      * @param subtype The new subtype of the new input method to be switched to.
+     * @throws IllegalArgumentException if the input method is unknown or filtered
+     * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
      */
     public final void switchInputMethod(String id, InputMethodSubtype subtype) {
         mPrivOps.setInputMethodAndSubtype(id, subtype);
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 976e71f..f93f9ab 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -34,6 +35,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.ParcelableHandwritingGesture;
+import android.view.inputmethod.PreviewableHandwritingGesture;
 import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.TextAttribute;
 import android.view.inputmethod.TextBoundsInfoResult;
@@ -427,6 +429,14 @@
     }
 
     @AnyThread
+    public boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return mInvoker.previewHandwritingGesture(ParcelableHandwritingGesture.of(gesture),
+                cancellationSignal);
+    }
+
+    @AnyThread
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         if (mCancellationGroup.isCanceled()) {
             return false;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 3c4abab..8e55692 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -911,11 +911,15 @@
             final String transactionName = getTransactionName(transactionCode);
             final StringBuffer buf = new StringBuffer();
 
+            // Keep trace name consistent with cpp trace name in:
+            // system/tools/aidl/generate_cpp.cpp
+            buf.append("AIDL::java::");
             if (transactionName != null) {
-                buf.append(mSimpleDescriptor).append(":").append(transactionName);
+                buf.append(mSimpleDescriptor).append("::").append(transactionName);
             } else {
-                buf.append(mSimpleDescriptor).append("#").append(transactionCode);
+                buf.append(mSimpleDescriptor).append("::#").append(transactionCode);
             }
+            buf.append("::server");
 
             transactionTraceName = buf.toString();
             mTransactionTraceNames.setRelease(index, transactionTraceName);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 933769a..8eaa5ad 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -69,6 +69,7 @@
     boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
     UserInfo getProfileParent(int userId);
     boolean isSameProfileGroup(int userId, int otherUserHandle);
+    boolean isHeadlessSystemUserMode();
     boolean isUserOfType(int userId, in String userType);
     @UnsupportedAppUsage
     UserInfo getUserInfo(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d84037f..1924dc6 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -70,3 +70,6 @@
 
 # Tracing
 per-file Trace.java = file:/TRACE_OWNERS
+
+# PermissionEnforcer
+per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f1879dd..3d20d63 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -91,7 +91,6 @@
 public class UserManager {
 
     private static final String TAG = "UserManager";
-    private static final boolean VERBOSE = false;
 
     @UnsupportedAppUsage
     private final IUserManager mService;
@@ -104,6 +103,9 @@
     /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
     private String mProfileTypeOfProcessUser = null;
 
+    /** Whether the device is in headless system user mode; null until cached. */
+    private static Boolean sIsHeadlessSystemUser = null;
+
     /**
      * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
      * This type of user cannot be created; it can only pre-exist on first boot.
@@ -2068,28 +2070,20 @@
      * @return whether the device is running in a headless system user mode.
      */
     public static boolean isHeadlessSystemUserMode() {
-        final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
-        if (!Build.isDebuggable()) {
-            return realMode;
+        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+        // (Its value is determined when UMS is constructed and cannot change.)
+        // Worst case we might end up calling the AIDL method multiple times but that's fine.
+        if (sIsHeadlessSystemUser == null) {
+            // Unfortunately this API is static, but the property no longer is. So go fetch the UMS.
+            try {
+                final IUserManager service = IUserManager.Stub.asInterface(
+                        ServiceManager.getService(Context.USER_SERVICE));
+                sIsHeadlessSystemUser = service.isHeadlessSystemUserMode();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
-
-        final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
-        switch (emulatedMode) {
-            case SYSTEM_USER_MODE_EMULATION_FULL:
-                if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false");
-                return false;
-            case SYSTEM_USER_MODE_EMULATION_HEADLESS:
-                if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true");
-                return true;
-            case SYSTEM_USER_MODE_EMULATION_DEFAULT:
-            case "": // property not set
-                return realMode;
-            default:
-                Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property "
-                        + SYSTEM_USER_MODE_EMULATION_PROPERTY + " (" + emulatedMode + "); using"
-                                + " default value (headless=" + realMode + ")");
-                return realMode;
-        }
+        return sIsHeadlessSystemUser;
     }
 
     /**
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 8534c66..16ae3bc 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -77,8 +77,7 @@
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
     void startOneTimePermissionSession(String packageName, int userId, long timeout,
-            long revokeAfterKilledDelay, int importanceToResetTimer,
-            int importanceToKeepSessionAlive);
+            long revokeAfterKilledDelay);
 
     @EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS")
     void stopOneTimePermissionSession(String packageName, int userId);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 6b540d7..6769954 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1371,8 +1371,7 @@
             @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
         try {
             mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
-                    timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
-                    importanceToKeepSessionAlive);
+                    timeoutMillis, revokeAfterKilledDelayMillis);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 3bf9ca0..b117a9a 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,9 @@
 
 package android.preference;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.app.NotificationManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.preference.VolumePreference.VolumeStore;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.System;
@@ -44,6 +47,7 @@
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.os.SomeArgs;
 
 import java.util.concurrent.TimeUnit;
@@ -115,7 +119,6 @@
     private final int mMaxStreamVolume;
     private boolean mAffectedByRingerMode;
     private boolean mNotificationOrRing;
-    private final boolean mNotifAliasRing;
     private final Receiver mReceiver = new Receiver();
 
     private Handler mHandler;
@@ -158,6 +161,7 @@
         this(context, streamType, defaultUri, callback, true /* playSample */);
     }
 
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public SeekBarVolumizer(
             Context context,
             int streamType,
@@ -180,8 +184,6 @@
         if (mNotificationOrRing) {
             mRingerMode = mAudioManager.getRingerModeInternal();
         }
-        mNotifAliasRing = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_alias_ring_notif_stream_types);
         mZenMode = mNotificationManager.getZenMode();
 
         if (hasAudioProductStrategies()) {
@@ -288,7 +290,9 @@
              * so that when user attempts to slide the notification seekbar out of vibrate the
              * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
              */
-            if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+            if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+                    || mStreamType == AudioManager.STREAM_RING
                     || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
                 mSeekBar.setProgress(0, true);
             }
@@ -365,7 +369,9 @@
         // set the time of stop volume
         if ((mStreamType == AudioManager.STREAM_VOICE_CALL
                 || mStreamType == AudioManager.STREAM_RING
-                || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
+                || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+                && mStreamType == AudioManager.STREAM_NOTIFICATION)
                 || mStreamType == AudioManager.STREAM_ALARM)) {
             sStopVolumeTime = java.lang.System.currentTimeMillis();
         }
@@ -644,8 +650,10 @@
         }
 
         private void updateVolumeSlider(int streamType, int streamValue) {
-            final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
-                    ? isNotificationOrRing(streamType) : streamType == mStreamType;
+            final boolean streamMatch =  !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+                    && mNotificationOrRing ? isNotificationOrRing(streamType) :
+                    streamType == mStreamType;
             if (mSeekBar != null && streamMatch && streamValue != -1) {
                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
                         || streamValue == 0;
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index 714afee..e64bdd6 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -74,39 +74,53 @@
     @Nullable
     private final String mFailureCause;
 
-    // Populated when mType == EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
+    // May be populated when EVENT_TYPE_SUGGESTION or EVENT_TYPE_UNCERTAIN
     @Nullable
     private final TimeZoneProviderStatus mTimeZoneProviderStatus;
 
-    private TimeZoneProviderEvent(int type,
+    private TimeZoneProviderEvent(@EventType int type,
             @ElapsedRealtimeLong long creationElapsedMillis,
             @Nullable TimeZoneProviderSuggestion suggestion,
             @Nullable String failureCause,
             @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
-        mType = type;
+        mType = validateEventType(type);
         mCreationElapsedMillis = creationElapsedMillis;
         mSuggestion = suggestion;
         mFailureCause = failureCause;
         mTimeZoneProviderStatus = timeZoneProviderStatus;
+
+        // Confirm the type and the provider status agree.
+        if (mType == EVENT_TYPE_PERMANENT_FAILURE && mTimeZoneProviderStatus != null) {
+            throw new IllegalArgumentException(
+                    "Unexpected status: mType=" + mType
+                            + ", mTimeZoneProviderStatus=" + mTimeZoneProviderStatus);
+        }
+    }
+
+    private static @EventType int validateEventType(@EventType int eventType) {
+        if (eventType < EVENT_TYPE_PERMANENT_FAILURE || eventType > EVENT_TYPE_UNCERTAIN) {
+            throw new IllegalArgumentException(Integer.toString(eventType));
+        }
+        return eventType;
     }
 
     /** Returns an event of type {@link #EVENT_TYPE_SUGGESTION}. */
     public static TimeZoneProviderEvent createSuggestionEvent(
             @ElapsedRealtimeLong long creationElapsedMillis,
             @NonNull TimeZoneProviderSuggestion suggestion,
-            @NonNull TimeZoneProviderStatus providerStatus) {
+            @Nullable TimeZoneProviderStatus providerStatus) {
         return new TimeZoneProviderEvent(EVENT_TYPE_SUGGESTION, creationElapsedMillis,
-                Objects.requireNonNull(suggestion), null, Objects.requireNonNull(providerStatus));
+                Objects.requireNonNull(suggestion), null, providerStatus);
     }
 
     /** Returns an event of type {@link #EVENT_TYPE_UNCERTAIN}. */
     public static TimeZoneProviderEvent createUncertainEvent(
             @ElapsedRealtimeLong long creationElapsedMillis,
-            @NonNull TimeZoneProviderStatus timeZoneProviderStatus) {
+            @Nullable TimeZoneProviderStatus timeZoneProviderStatus) {
 
         return new TimeZoneProviderEvent(
                 EVENT_TYPE_UNCERTAIN, creationElapsedMillis, null, null,
-                Objects.requireNonNull(timeZoneProviderStatus));
+                timeZoneProviderStatus);
     }
 
     /** Returns an event of type {@link #EVENT_TYPE_PERMANENT_FAILURE}. */
@@ -148,8 +162,8 @@
     }
 
     /**
-     * Returns the status of the time zone provider. Populated when {@link #getType()} is {@link
-     * #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}.
+     * Returns the status of the time zone provider.  May be populated when {@link #getType()} is
+     * {@link #EVENT_TYPE_UNCERTAIN} or {@link #EVENT_TYPE_SUGGESTION}, otherwise {@code null}.
      */
     @Nullable
     public TimeZoneProviderStatus getTimeZoneProviderStatus() {
diff --git a/core/java/android/service/timezone/TimeZoneProviderService.java b/core/java/android/service/timezone/TimeZoneProviderService.java
index cd4a305..2cea95a 100644
--- a/core/java/android/service/timezone/TimeZoneProviderService.java
+++ b/core/java/android/service/timezone/TimeZoneProviderService.java
@@ -203,7 +203,8 @@
      * details.
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion) {
-        reportSuggestion(suggestion, TimeZoneProviderStatus.UNKNOWN);
+        TimeZoneProviderStatus providerStatus = null;
+        reportSuggestionInternal(suggestion, providerStatus);
     }
 
     /**
@@ -217,6 +218,12 @@
      */
     public final void reportSuggestion(@NonNull TimeZoneProviderSuggestion suggestion,
             @NonNull TimeZoneProviderStatus providerStatus) {
+        Objects.requireNonNull(providerStatus);
+        reportSuggestionInternal(suggestion, providerStatus);
+    }
+
+    private void reportSuggestionInternal(@NonNull TimeZoneProviderSuggestion suggestion,
+            @Nullable TimeZoneProviderStatus providerStatus) {
         Objects.requireNonNull(suggestion);
 
         mHandler.post(() -> {
@@ -245,7 +252,8 @@
      * to a time zone.
      */
     public final void reportUncertain() {
-        reportUncertain(TimeZoneProviderStatus.UNKNOWN);
+        TimeZoneProviderStatus providerStatus = null;
+        reportUncertainInternal(providerStatus);
     }
 
     /**
@@ -260,6 +268,11 @@
      * @hide
      */
     public final void reportUncertain(@NonNull TimeZoneProviderStatus providerStatus) {
+        Objects.requireNonNull(providerStatus);
+        reportUncertainInternal(providerStatus);
+    }
+
+    private void reportUncertainInternal(@Nullable TimeZoneProviderStatus providerStatus) {
         mHandler.post(() -> {
             synchronized (mLock) {
                 ITimeZoneProviderManager manager = mManager;
diff --git a/core/java/android/service/timezone/TimeZoneProviderStatus.java b/core/java/android/service/timezone/TimeZoneProviderStatus.java
index 87d7843..513068f 100644
--- a/core/java/android/service/timezone/TimeZoneProviderStatus.java
+++ b/core/java/android/service/timezone/TimeZoneProviderStatus.java
@@ -18,14 +18,18 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Information about the status of a {@link TimeZoneProviderService}.
@@ -71,7 +75,7 @@
     @IntDef(prefix = "DEPENDENCY_STATUS_", value = {
             DEPENDENCY_STATUS_UNKNOWN,
             DEPENDENCY_STATUS_NOT_APPLICABLE,
-            DEPENDENCY_STATUS_WORKING,
+            DEPENDENCY_STATUS_OK,
             DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE,
             DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT,
             DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS,
@@ -81,14 +85,18 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface DependencyStatus {}
 
-    /** The dependency's status is unknown. */
+    /**
+     * The dependency's status is unknown.
+     *
+     * @hide
+     */
     public static final @DependencyStatus int DEPENDENCY_STATUS_UNKNOWN = 0;
 
     /** The dependency is not used by the provider's implementation. */
     public static final @DependencyStatus int DEPENDENCY_STATUS_NOT_APPLICABLE = 1;
 
-    /** The dependency is applicable and working well. */
-    public static final @DependencyStatus int DEPENDENCY_STATUS_WORKING = 2;
+    /** The dependency is applicable and there are no known problems. */
+    public static final @DependencyStatus int DEPENDENCY_STATUS_OK = 2;
 
     /**
      * The dependency is used but is temporarily unavailable, e.g. connectivity has been lost for an
@@ -136,76 +144,105 @@
     @IntDef(prefix = "OPERATION_STATUS_", value = {
             OPERATION_STATUS_UNKNOWN,
             OPERATION_STATUS_NOT_APPLICABLE,
-            OPERATION_STATUS_WORKING,
+            OPERATION_STATUS_OK,
             OPERATION_STATUS_FAILED,
     })
     @Target(ElementType.TYPE_USE)
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationStatus {}
 
-    /** The operation's status is unknown. */
+    /**
+     * The operation's status is unknown.
+     *
+     * @hide
+     */
     public static final @OperationStatus int OPERATION_STATUS_UNKNOWN = 0;
 
     /** The operation is not used by the provider's implementation. */
     public static final @OperationStatus int OPERATION_STATUS_NOT_APPLICABLE = 1;
 
-    /** The operation is applicable and working well. */
-    public static final @OperationStatus int OPERATION_STATUS_WORKING = 2;
+    /** The operation is applicable and there are no known problems. */
+    public static final @OperationStatus int OPERATION_STATUS_OK = 2;
 
-    /** The operation is applicable and failed. */
+    /** The operation is applicable and it recently failed. */
     public static final @OperationStatus int OPERATION_STATUS_FAILED = 3;
 
-    /**
-     * An instance that provides no information about status. Effectively a "null" status.
-     */
-    @NonNull
-    public static final TimeZoneProviderStatus UNKNOWN = new TimeZoneProviderStatus(
-            DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_UNKNOWN, OPERATION_STATUS_UNKNOWN);
-
-    private final @DependencyStatus int mLocationDetectionStatus;
-    private final @DependencyStatus int mConnectivityStatus;
-    private final @OperationStatus int mTimeZoneResolutionStatus;
+    private final @DependencyStatus int mLocationDetectionDependencyStatus;
+    private final @DependencyStatus int mConnectivityDependencyStatus;
+    private final @OperationStatus int mTimeZoneResolutionOperationStatus;
 
     private TimeZoneProviderStatus(
             @DependencyStatus int locationDetectionStatus,
             @DependencyStatus int connectivityStatus,
             @OperationStatus int timeZoneResolutionStatus) {
-        mLocationDetectionStatus = requireValidDependencyStatus(locationDetectionStatus);
-        mConnectivityStatus = requireValidDependencyStatus(connectivityStatus);
-        mTimeZoneResolutionStatus = requireValidOperationStatus(timeZoneResolutionStatus);
+        mLocationDetectionDependencyStatus = locationDetectionStatus;
+        mConnectivityDependencyStatus = connectivityStatus;
+        mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus;
     }
 
     /**
      * Returns the status of the location detection dependencies used by the provider (where
      * applicable).
      */
-    public @DependencyStatus int getLocationDetectionStatus() {
-        return mLocationDetectionStatus;
+    public @DependencyStatus int getLocationDetectionDependencyStatus() {
+        return mLocationDetectionDependencyStatus;
     }
 
     /**
      * Returns the status of the connectivity dependencies used by the provider (where applicable).
      */
-    public @DependencyStatus int getConnectivityStatus() {
-        return mConnectivityStatus;
+    public @DependencyStatus int getConnectivityDependencyStatus() {
+        return mConnectivityDependencyStatus;
     }
 
     /**
      * Returns the status of the time zone resolution operation used by the provider.
      */
-    public @OperationStatus int getTimeZoneResolutionStatus() {
-        return mTimeZoneResolutionStatus;
+    public @OperationStatus int getTimeZoneResolutionOperationStatus() {
+        return mTimeZoneResolutionOperationStatus;
     }
 
     @Override
     public String toString() {
         return "TimeZoneProviderStatus{"
-                + "mLocationDetectionStatus=" + mLocationDetectionStatus
-                + ", mConnectivityStatus=" + mConnectivityStatus
-                + ", mTimeZoneResolutionStatus=" + mTimeZoneResolutionStatus
+                + "mLocationDetectionDependencyStatus="
+                + dependencyStatusToString(mLocationDetectionDependencyStatus)
+                + ", mConnectivityDependencyStatus="
+                + dependencyStatusToString(mConnectivityDependencyStatus)
+                + ", mTimeZoneResolutionOperationStatus="
+                + operationStatusToString(mTimeZoneResolutionOperationStatus)
                 + '}';
     }
 
+    /**
+     * Parses a {@link TimeZoneProviderStatus} from a toString() string for manual command-line
+     * testing.
+     *
+     * @hide
+     */
+    @NonNull
+    public static TimeZoneProviderStatus parseProviderStatus(@NonNull String arg) {
+        // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+        // on OpenJDK code.
+        Pattern pattern = Pattern.compile("TimeZoneProviderStatus\\{"
+                + "mLocationDetectionDependencyStatus=([^,]+)"
+                + ", mConnectivityDependencyStatus=([^,]+)"
+                + ", mTimeZoneResolutionOperationStatus=([^\\}]+)"
+                + "\\}");
+        Matcher matcher = pattern.matcher(arg);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Unable to parse provider status: " + arg);
+        }
+        @DependencyStatus int locationDependencyStatus =
+                dependencyStatusFromString(matcher.group(1));
+        @DependencyStatus int connectivityDependencyStatus =
+                dependencyStatusFromString(matcher.group(2));
+        @OperationStatus int timeZoneResolutionOperationStatus =
+                operationStatusFromString(matcher.group(3));
+        return new TimeZoneProviderStatus(locationDependencyStatus, connectivityDependencyStatus,
+                timeZoneResolutionOperationStatus);
+    }
+
     public static final @NonNull Creator<TimeZoneProviderStatus> CREATOR = new Creator<>() {
         @Override
         public TimeZoneProviderStatus createFromParcel(Parcel in) {
@@ -229,9 +266,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        parcel.writeInt(mLocationDetectionStatus);
-        parcel.writeInt(mConnectivityStatus);
-        parcel.writeInt(mTimeZoneResolutionStatus);
+        parcel.writeInt(mLocationDetectionDependencyStatus);
+        parcel.writeInt(mConnectivityDependencyStatus);
+        parcel.writeInt(mTimeZoneResolutionOperationStatus);
     }
 
     @Override
@@ -243,23 +280,33 @@
             return false;
         }
         TimeZoneProviderStatus that = (TimeZoneProviderStatus) o;
-        return mLocationDetectionStatus == that.mLocationDetectionStatus
-                && mConnectivityStatus == that.mConnectivityStatus
-                && mTimeZoneResolutionStatus == that.mTimeZoneResolutionStatus;
+        return mLocationDetectionDependencyStatus == that.mLocationDetectionDependencyStatus
+                && mConnectivityDependencyStatus == that.mConnectivityDependencyStatus
+                && mTimeZoneResolutionOperationStatus == that.mTimeZoneResolutionOperationStatus;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(
-                mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+                mLocationDetectionDependencyStatus, mConnectivityDependencyStatus,
+                mTimeZoneResolutionOperationStatus);
+    }
+
+    /** @hide */
+    public boolean couldEnableTelephonyFallback() {
+        return mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                || mLocationDetectionDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS
+                || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT
+                || mConnectivityDependencyStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
     }
 
     /** A builder for {@link TimeZoneProviderStatus}. */
     public static final class Builder {
 
-        private @DependencyStatus int mLocationDetectionStatus = DEPENDENCY_STATUS_UNKNOWN;
-        private @DependencyStatus int mConnectivityStatus = DEPENDENCY_STATUS_UNKNOWN;
-        private @OperationStatus int mTimeZoneResolutionStatus = OPERATION_STATUS_UNKNOWN;
+        private @DependencyStatus int mLocationDetectionDependencyStatus =
+                DEPENDENCY_STATUS_UNKNOWN;
+        private @DependencyStatus int mConnectivityDependencyStatus = DEPENDENCY_STATUS_UNKNOWN;
+        private @OperationStatus int mTimeZoneResolutionOperationStatus = OPERATION_STATUS_UNKNOWN;
 
         /**
          * Creates a new builder instance. At creation time all status properties are set to
@@ -272,9 +319,9 @@
          * @hide
          */
         public Builder(TimeZoneProviderStatus toCopy) {
-            mLocationDetectionStatus = toCopy.mLocationDetectionStatus;
-            mConnectivityStatus = toCopy.mConnectivityStatus;
-            mTimeZoneResolutionStatus = toCopy.mTimeZoneResolutionStatus;
+            mLocationDetectionDependencyStatus = toCopy.mLocationDetectionDependencyStatus;
+            mConnectivityDependencyStatus = toCopy.mConnectivityDependencyStatus;
+            mTimeZoneResolutionOperationStatus = toCopy.mTimeZoneResolutionOperationStatus;
         }
 
         /**
@@ -282,8 +329,9 @@
          * See the {@code DEPENDENCY_STATUS_} constants for more information.
          */
         @NonNull
-        public Builder setLocationDetectionStatus(@DependencyStatus int locationDetectionStatus) {
-            mLocationDetectionStatus = locationDetectionStatus;
+        public Builder setLocationDetectionDependencyStatus(
+                @DependencyStatus int locationDetectionStatus) {
+            mLocationDetectionDependencyStatus = locationDetectionStatus;
             return this;
         }
 
@@ -292,8 +340,8 @@
          * See the {@code DEPENDENCY_STATUS_} constants for more information.
          */
         @NonNull
-        public Builder setConnectivityStatus(@DependencyStatus int connectivityStatus) {
-            mConnectivityStatus = connectivityStatus;
+        public Builder setConnectivityDependencyStatus(@DependencyStatus int connectivityStatus) {
+            mConnectivityDependencyStatus = connectivityStatus;
             return this;
         }
 
@@ -302,8 +350,9 @@
          * See the {@code OPERATION_STATUS_} constants for more information.
          */
         @NonNull
-        public Builder setTimeZoneResolutionStatus(@OperationStatus int timeZoneResolutionStatus) {
-            mTimeZoneResolutionStatus = timeZoneResolutionStatus;
+        public Builder setTimeZoneResolutionOperationStatus(
+                @OperationStatus int timeZoneResolutionStatus) {
+            mTimeZoneResolutionOperationStatus = timeZoneResolutionStatus;
             return this;
         }
 
@@ -313,11 +362,14 @@
         @NonNull
         public TimeZoneProviderStatus build() {
             return new TimeZoneProviderStatus(
-                    mLocationDetectionStatus, mConnectivityStatus, mTimeZoneResolutionStatus);
+                    requireValidDependencyStatus(mLocationDetectionDependencyStatus),
+                    requireValidDependencyStatus(mConnectivityDependencyStatus),
+                    requireValidOperationStatus(mTimeZoneResolutionOperationStatus));
         }
     }
 
-    private @OperationStatus int requireValidOperationStatus(@OperationStatus int operationStatus) {
+    private static @OperationStatus int requireValidOperationStatus(
+            @OperationStatus int operationStatus) {
         if (operationStatus < OPERATION_STATUS_UNKNOWN
                 || operationStatus > OPERATION_STATUS_FAILED) {
             throw new IllegalArgumentException(Integer.toString(operationStatus));
@@ -325,6 +377,45 @@
         return operationStatus;
     }
 
+    /** @hide */
+    @NonNull
+    public static String operationStatusToString(@OperationStatus int operationStatus) {
+        switch (operationStatus) {
+            case OPERATION_STATUS_UNKNOWN:
+                return "UNKNOWN";
+            case OPERATION_STATUS_NOT_APPLICABLE:
+                return "NOT_APPLICABLE";
+            case OPERATION_STATUS_OK:
+                return "OK";
+            case OPERATION_STATUS_FAILED:
+                return "FAILED";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + operationStatus);
+        }
+    }
+
+    /** @hide */
+    public static @OperationStatus int operationStatusFromString(
+            @Nullable String operationStatusString) {
+
+        if (TextUtils.isEmpty(operationStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + operationStatusString);
+        }
+
+        switch (operationStatusString) {
+            case "UNKNOWN":
+                return OPERATION_STATUS_UNKNOWN;
+            case "NOT_APPLICABLE":
+                return OPERATION_STATUS_NOT_APPLICABLE;
+            case "OK":
+                return OPERATION_STATUS_OK;
+            case "FAILED":
+                return OPERATION_STATUS_FAILED;
+            default:
+                throw new IllegalArgumentException("Unknown status: " + operationStatusString);
+        }
+    }
+
     private static @DependencyStatus int requireValidDependencyStatus(
             @DependencyStatus int dependencyStatus) {
         if (dependencyStatus < DEPENDENCY_STATUS_UNKNOWN
@@ -333,4 +424,56 @@
         }
         return dependencyStatus;
     }
+
+    /** @hide */
+    @NonNull
+    public static String dependencyStatusToString(@DependencyStatus int dependencyStatus) {
+        switch (dependencyStatus) {
+            case DEPENDENCY_STATUS_UNKNOWN:
+                return "UNKNOWN";
+            case DEPENDENCY_STATUS_NOT_APPLICABLE:
+                return "NOT_APPLICABLE";
+            case DEPENDENCY_STATUS_OK:
+                return "OK";
+            case DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE:
+                return "TEMPORARILY_UNAVAILABLE";
+            case DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT:
+                return "BLOCKED_BY_ENVIRONMENT";
+            case DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS:
+                return "DEGRADED_BY_SETTINGS";
+            case DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS:
+                return "BLOCKED_BY_SETTINGS";
+            default:
+                throw new IllegalArgumentException("Unknown status: " + dependencyStatus);
+        }
+    }
+
+    /** @hide */
+    public static @DependencyStatus int dependencyStatusFromString(
+            @Nullable String dependencyStatusString) {
+
+        if (TextUtils.isEmpty(dependencyStatusString)) {
+            throw new IllegalArgumentException("Empty status: " + dependencyStatusString);
+        }
+
+        switch (dependencyStatusString) {
+            case "UNKNOWN":
+                return DEPENDENCY_STATUS_UNKNOWN;
+            case "NOT_APPLICABLE":
+                return DEPENDENCY_STATUS_NOT_APPLICABLE;
+            case "OK":
+                return DEPENDENCY_STATUS_OK;
+            case "TEMPORARILY_UNAVAILABLE":
+                return DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
+            case "BLOCKED_BY_ENVIRONMENT":
+                return DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
+            case "DEGRADED_BY_SETTINGS":
+                return DEPENDENCY_STATUS_DEGRADED_BY_SETTINGS;
+            case "BLOCKED_BY_SETTINGS":
+                return DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+            default:
+                throw new IllegalArgumentException(
+                        "Unknown status: " + dependencyStatusString);
+        }
+    }
 }
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
index 1c4d14c91..bf8ee47 100644
--- a/core/java/android/service/voice/HotwordAudioStream.java
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -127,6 +127,16 @@
         }
     }
 
+    /**
+     * Provides an instance of {@link Builder} with state corresponding to this instance.
+     * @hide
+     */
+    public Builder buildUpon() {
+        return new Builder(mAudioFormat, mAudioStreamParcelFileDescriptor)
+            .setTimestamp(mTimestamp)
+            .setMetadata(mMetadata);
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -439,10 +449,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665976240224L,
+            time = 1666342101364L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java",
-            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+            inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static  android.media.AudioTimestamp defaultTimestamp()\nprivate static  android.os.PersistableBundle defaultMetadata()\nprivate  java.lang.String timestampToString()\nprivate  void parcelTimestamp(android.os.Parcel,int)\nprivate static @android.annotation.Nullable android.media.AudioTimestamp unparcelTimestamp(android.os.Parcel)\npublic  android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index e22bbd8..b4f20c9 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -388,6 +388,25 @@
         }
     }
 
+    /**
+     * Provides an instance of {@link Builder} with state corresponding to this instance.
+     * @hide
+     */
+    public Builder buildUpon() {
+        return new Builder()
+            .setConfidenceLevel(mConfidenceLevel)
+            .setMediaSyncEvent(mMediaSyncEvent)
+            .setHotwordOffsetMillis(mHotwordOffsetMillis)
+            .setHotwordDurationMillis(mHotwordDurationMillis)
+            .setAudioChannel(mAudioChannel)
+            .setHotwordDetectionPersonalized(mHotwordDetectionPersonalized)
+            .setScore(mScore)
+            .setPersonalizedScore(mPersonalizedScore)
+            .setHotwordPhraseId(mHotwordPhraseId)
+            .setAudioStreams(mAudioStreams)
+            .setExtras(mExtras);
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -984,10 +1003,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665995595979L,
+            time = 1666342044844L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final  int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final  int HOTWORD_OFFSET_UNSET\npublic static final  int AUDIO_CHANNEL_UNSET\nprivate static final  int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final  int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final  java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate  int mHotwordOffsetMillis\nprivate  int mHotwordDurationMillis\nprivate  int mAudioChannel\nprivate  boolean mHotwordDetectionPersonalized\nprivate final  int mScore\nprivate final  int mPersonalizedScore\nprivate final  int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static  int sMaxBundleSize\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultScore()\nprivate static  int defaultPersonalizedScore()\npublic static  int getMaxScore()\nprivate static  int defaultHotwordPhraseId()\npublic static  int getMaxHotwordPhraseId()\nprivate static  java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static  android.os.PersistableBundle defaultExtras()\npublic static  int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static  int getParcelableSize(android.os.Parcelable)\npublic static  int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static  int bitCount(long)\nprivate  void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic  android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1285d1e..7c125c7 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -349,7 +349,7 @@
      * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
      * AlwaysOnHotwordDetector.Callback)} or {@link #createHotwordDetector(PersistableBundle,
      * SharedMemory, HotwordDetector.Callback)}, call this will throw an
-     * {@link IllegalArgumentException}.
+     * {@link IllegalStateException}.
      *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
@@ -385,7 +385,7 @@
      *
      * <p>Note: If there are any active detectors that are created by using
      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
-     * call this will throw an {@link IllegalArgumentException}.
+     * call this will throw an {@link IllegalStateException}.
      *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
@@ -428,13 +428,18 @@
             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
                 // Allow only one concurrent recognition via the APIs.
                 safelyShutdownAllHotwordDetectors();
-            }
-
-            for (HotwordDetector detector : mActiveHotwordDetectors) {
-                if (detector.isUsingHotwordDetectionService() != supportHotwordDetectionService) {
-                    throw new IllegalArgumentException(
-                            "It disallows to create trusted and non-trusted detectors "
-                                    + "at the same time.");
+            } else {
+                for (HotwordDetector detector : mActiveHotwordDetectors) {
+                    if (detector.isUsingHotwordDetectionService()
+                            != supportHotwordDetectionService) {
+                        throw new IllegalStateException(
+                                "It disallows to create trusted and non-trusted detectors "
+                                        + "at the same time.");
+                    } else if (detector instanceof AlwaysOnHotwordDetector) {
+                        throw new IllegalStateException(
+                                "There is already an active AlwaysOnHotwordDetector. "
+                                        + "It must be destroyed to create a new one.");
+                    }
                 }
             }
 
@@ -442,11 +447,7 @@
                     callback, mKeyphraseEnrollmentInfo, mSystemService,
                     getApplicationContext().getApplicationInfo().targetSdkVersion,
                     supportHotwordDetectionService);
-            if (!mActiveHotwordDetectors.add(dspDetector)) {
-                throw new IllegalArgumentException(
-                        "the keyphrase=" + keyphrase + " and locale=" + locale
-                                + " are already used by another always-on detector");
-            }
+            mActiveHotwordDetectors.add(dspDetector);
 
             try {
                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
@@ -480,7 +481,7 @@
      *
      * <p>Note: If there are any active detectors that are created by using
      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
-     * call this will throw an {@link IllegalArgumentException}.
+     * call this will throw an {@link IllegalStateException}.
      *
      * @param options Application configuration data to be provided to the
      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
@@ -513,11 +514,11 @@
             } else {
                 for (HotwordDetector detector : mActiveHotwordDetectors) {
                     if (!detector.isUsingHotwordDetectionService()) {
-                        throw new IllegalArgumentException(
+                        throw new IllegalStateException(
                                 "It disallows to create trusted and non-trusted detectors "
                                         + "at the same time.");
                     } else if (detector instanceof SoftwareHotwordDetector) {
-                        throw new IllegalArgumentException(
+                        throw new IllegalStateException(
                                 "There is already an active SoftwareHotwordDetector. "
                                         + "It must be destroyed to create a new one.");
                     }
@@ -527,6 +528,7 @@
             SoftwareHotwordDetector softwareHotwordDetector =
                     new SoftwareHotwordDetector(
                             mSystemService, null, callback);
+            mActiveHotwordDetectors.add(softwareHotwordDetector);
 
             try {
                 softwareHotwordDetector.registerOnDestroyListener(
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 1148120..6f8c5db 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -224,7 +224,7 @@
         byte[] resultBytes = messageDigest.digest();
 
         if (separator == null) {
-            return HexEncoding.encodeToString(resultBytes, true);
+            return HexEncoding.encodeToString(resultBytes, false);
         }
 
         int length = resultBytes.length;
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index c8c1fd4..eb467e0 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -93,8 +93,9 @@
      * associated with each signer.
      *
      * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
-     * @throws SecurityException if a APK Signature Scheme v2 signature of this APK does not verify.
-     * @throws IOException if an I/O error occurs while reading the APK file.
+     * @throws SecurityException          if an APK Signature Scheme v2 signature of this APK does
+     *                                    not verify.
+     * @throws IOException                if an I/O error occurs while reading the APK file.
      */
     public static X509Certificate[][] verify(String apkFile)
             throws SignatureNotFoundException, SecurityException, IOException {
@@ -386,7 +387,6 @@
                     break;
             }
         }
-        return;
     }
 
     static byte[] getVerityRootHash(String apkPath)
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 332e97c..02e0fcc 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -65,7 +65,7 @@
     public void onWindowFocusGained(boolean hasViewFocus) {
         super.onWindowFocusGained(hasViewFocus);
         getImm().registerImeConsumer(this);
-        if (isRequestedVisible() && getControl() == null) {
+        if ((mController.getRequestedVisibleTypes() & getType()) != 0 && getControl() == null) {
             mIsRequestedVisibleAwaitingControl = true;
         }
     }
@@ -125,7 +125,7 @@
         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
         // this code here means that we now got control, so we can start the animation immediately.
         // If client window is trying to control IME and IME is already visible, it is immediate.
-        if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) {
+        if (fromIme || (mState.getSource(getInternalType()).isVisible() && getControl() != null)) {
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
@@ -169,7 +169,7 @@
 
     @Override
     protected boolean isRequestedVisibleAwaitingControl() {
-        return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
+        return super.isRequestedVisibleAwaitingControl() || mIsRequestedVisibleAwaitingControl;
     }
 
     @Override
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 799955b..3d7843c 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -219,8 +219,9 @@
 
     /**
      * The input source is a mouse pointing device.
-     * This code is also used for other mouse-like pointing devices such as trackpads
-     * and trackpoints.
+     * This value is also used for other mouse-like pointing devices such as touchpads and pointing
+     * sticks. When used in combination with {@link #SOURCE_STYLUS}, it denotes an external drawing
+     * tablet.
      *
      * @see #SOURCE_CLASS_POINTER
      */
@@ -291,8 +292,8 @@
     public static final int SOURCE_MOUSE_RELATIVE = 0x00020000 | SOURCE_CLASS_TRACKBALL;
 
     /**
-     * The input source is a touch pad or digitizer tablet that is not
-     * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
+     * The input source is a touchpad (also known as a trackpad). Touchpads that are used to move
+     * the mouse cursor will also have {@link #SOURCE_MOUSE}.
      *
      * @see #SOURCE_CLASS_POSITION
      */
@@ -779,7 +780,7 @@
      * same input device descriptor.  This might happen in situations where a single
      * human input device registers multiple {@link InputDevice} instances (HID collections)
      * that describe separate features of the device, such as a keyboard that also
-     * has a trackpad.  Alternately, it may be that the input devices are simply
+     * has a touchpad.  Alternately, it may be that the input devices are simply
      * indistinguishable, such as two keyboards made by the same manufacturer.
      * </p><p>
      * The input device descriptor returned by {@link #getDescriptor} should only be
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 805727c..27b4d87 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -128,7 +128,7 @@
                     null /* typeSideMap */);
             mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
                     typeSideMap);
-            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
             if (mHasZeroInsetsIme) {
                 // IME has shownInsets of ZERO, and can't map to a side by default.
                 // Map zero insets IME to bottom, making it a special case of bottom insets.
@@ -141,7 +141,7 @@
             mCurrentInsets = calculateInsets(mInitialInsetsState, controls, true /* shown */);
             mHiddenInsets = calculateInsets(null, controls, false /* shown */);
             mShownInsets = calculateInsets(null, controls, true /* shown */);
-            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME);
+            mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
             buildSideControlsMap(mSideControlsMap, controls);
         }
         mPendingInsets = mCurrentInsets;
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 1cb00e3..291351e 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -19,7 +19,6 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsController.AnimationType;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets.Type.InsetsType;
 
 /**
@@ -63,10 +62,10 @@
     WindowInsetsAnimation getAnimation();
 
     /**
-     * @return Whether {@link #getTypes()} maps to a specific {@link InternalInsetsType}.
+     * @return Whether {@link #getTypes()} contains a specific {@link InsetsType}.
      */
-    default boolean controlsInternalType(@InternalInsetsType int type) {
-        return InsetsState.toInternalType(getTypes()).contains(type);
+    default boolean controlsType(@InsetsType int type) {
+        return (getTypes() & type) != 0;
     }
 
     /**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8b38e9e..35838a3 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -24,6 +24,8 @@
 import static android.view.InsetsState.toInternalType;
 import static android.view.InsetsState.toPublicType;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.ime;
 
@@ -744,8 +746,8 @@
         for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
             InsetsSource source = newState.peekSource(type);
             if (source == null) continue;
-            @AnimationType int animationType = getAnimationType(type);
             @InsetsType int insetsType = toPublicType(type);
+            @AnimationType int animationType = getAnimationType(insetsType);
             if (!source.isUserControllable()) {
                 // The user animation is not allowed when visible frame is empty.
                 disabledUserAnimationTypes |= insetsType;
@@ -788,8 +790,7 @@
         if (diff != 0) {
             for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
                 InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-                if (consumer.getControl() != null
-                        && (toPublicType(consumer.getType()) & diff) != 0) {
+                if (consumer.getControl() != null && (consumer.getType() & diff) != 0) {
                     mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
                     mHandler.post(mInvokeControllableInsetsChangedListeners);
                     break;
@@ -897,7 +898,7 @@
         // Ensure to update all existing source consumers
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            final InsetsSourceControl control = mTmpControlArray.get(consumer.getType());
+            final InsetsSourceControl control = mTmpControlArray.get(consumer.getInternalType());
 
             // control may be null, but we still need to update the control to null if it got
             // revoked.
@@ -985,25 +986,26 @@
         // TODO: Support a ResultReceiver for IME.
         // TODO(b/123718661): Make show() work for multi-session IME.
         int typesReady = 0;
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            @InternalInsetsType int internalType = internalTypes.valueAt(i);
-            @AnimationType int animationType = getAnimationType(internalType);
-            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
-            if (consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
+        for (int type = FIRST; type <= LAST; type = type << 1) {
+            if ((types & type) == 0) {
+                continue;
+            }
+            final @AnimationType int animationType = getAnimationType(type);
+            final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+            if (requestedVisible && animationType == ANIMATION_TYPE_NONE
                     || animationType == ANIMATION_TYPE_SHOW) {
                 // no-op: already shown or animating in (because window visibility is
                 // applied before starting animation).
                 if (DEBUG) Log.d(TAG, String.format(
                         "show ignored for type: %d animType: %d requestedVisible: %s",
-                        consumer.getType(), animationType, consumer.isRequestedVisible()));
+                        type, animationType, requestedVisible));
                 continue;
             }
             if (fromIme && animationType == ANIMATION_TYPE_USER) {
                 // App is already controlling the IME, don't cancel it.
                 continue;
             }
-            typesReady |= InsetsState.toPublicType(consumer.getType());
+            typesReady |= type;
         }
         if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
         applyAnimation(typesReady, true /* show */, fromIme);
@@ -1024,17 +1026,18 @@
             Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
         }
         int typesReady = 0;
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            @InternalInsetsType int internalType = internalTypes.valueAt(i);
-            @AnimationType int animationType = getAnimationType(internalType);
-            InsetsSourceConsumer consumer = getSourceConsumer(internalType);
-            if (!consumer.isRequestedVisible() && animationType == ANIMATION_TYPE_NONE
+        for (int type = FIRST; type <= LAST; type = type << 1) {
+            if ((types & type) == 0) {
+                continue;
+            }
+            final @AnimationType int animationType = getAnimationType(type);
+            final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
+            if (!requestedVisible && animationType == ANIMATION_TYPE_NONE
                     || animationType == ANIMATION_TYPE_HIDE) {
                 // no-op: already hidden or animating out.
                 continue;
             }
-            typesReady |= InsetsState.toPublicType(consumer.getType());
+            typesReady |= type;
         }
         applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
     }
@@ -1228,13 +1231,13 @@
             if (!canRun) {
                 if (WARN) Log.w(TAG, String.format(
                         "collectSourceControls can't continue show for type: %s fromIme: %b",
-                        InsetsState.typeToString(consumer.getType()), fromIme));
+                        InsetsState.typeToString(consumer.getInternalType()), fromIme));
                 continue;
             }
             final InsetsSourceControl control = consumer.getControl();
             if (control != null && control.getLeash() != null) {
-                controls.put(consumer.getType(), new InsetsSourceControl(control));
-                typesReady |= toPublicType(consumer.getType());
+                controls.put(control.getType(), new InsetsSourceControl(control));
+                typesReady |= consumer.getType();
             } else if (animationType == ANIMATION_TYPE_SHOW) {
                 if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
                         + fromIme);
@@ -1260,25 +1263,15 @@
 
     private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
             @InsetsType int types) {
-
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-
         // Generally, we want to layout the opposite of the current state. This is to make animation
         // callbacks easy to use: The can capture the layout values and then treat that as end-state
         // during the animation.
         //
         // However, if controlling multiple sources, we want to treat it as shown if any of the
         // types is currently hidden.
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
-            if (consumer == null) {
-                continue;
-            }
-            if (!consumer.isRequestedVisible()) {
-                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-            }
-        }
-        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+        return (mRequestedVisibleTypes & types) != types
+                ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
     }
 
     private void cancelExistingControllers(@InsetsType int types) {
@@ -1332,15 +1325,15 @@
     }
 
     void notifyControlRevoked(InsetsSourceConsumer consumer) {
-        final @InsetsType int types = toPublicType(consumer.getType());
+        final @InsetsType int type = consumer.getType();
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
-            control.notifyControlRevoked(types);
+            control.notifyControlRevoked(type);
             if (control.getControllingTypes() == 0) {
                 cancelAnimation(control, true /* invokeCallback */);
             }
         }
-        if (consumer.getType() == ITYPE_IME) {
+        if (type == ime()) {
             abortPendingImeControlRequest();
         }
     }
@@ -1425,27 +1418,25 @@
     }
 
     @VisibleForTesting
-    public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+    public @AnimationType int getAnimationType(@InsetsType int type) {
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
-            if (control.controlsInternalType(type)) {
+            if (control.controlsType(type)) {
                 return mRunningAnimations.get(i).type;
             }
         }
         return ANIMATION_TYPE_NONE;
     }
 
-    @VisibleForTesting
-    public void onRequestedVisibilityChanged(InsetsSourceConsumer consumer) {
-        final @InsetsType int type = InsetsState.toPublicType(consumer.getType());
-        final int requestedVisibleTypes = consumer.isRequestedVisible()
-                ? mRequestedVisibleTypes | type
-                : mRequestedVisibleTypes & ~type;
+    void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+        final @InsetsType int requestedVisibleTypes =
+                (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
         if (mRequestedVisibleTypes != requestedVisibleTypes) {
-            mRequestedVisibleTypes = requestedVisibleTypes;
-            if (WindowInsets.Type.hasCompatSystemBars(type)) {
+            if (WindowInsets.Type.hasCompatSystemBars(
+                    mRequestedVisibleTypes ^ requestedVisibleTypes)) {
                 mCompatSysUiVisibilityStaled = true;
             }
+            mRequestedVisibleTypes = requestedVisibleTypes;
         }
     }
 
@@ -1664,9 +1655,9 @@
         @InsetsType int result = 0;
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            InsetsSource source = mState.peekSource(consumer.mType);
+            InsetsSource source = mState.peekSource(consumer.getInternalType());
             if (consumer.getControl() != null && source != null && source.isUserControllable()) {
-                result |= toPublicType(consumer.mType);
+                result |= consumer.getType();
             }
         }
         return result & ~mState.calculateUncontrollableInsetsFromFrame(mFrame);
@@ -1707,12 +1698,11 @@
     }
 
     @Override
-    public void reportPerceptible(int types, boolean perceptible) {
-        final ArraySet<Integer> internalTypes = toInternalType(types);
+    public void reportPerceptible(@InsetsType int types, boolean perceptible) {
         final int size = mSourceConsumers.size();
         for (int i = 0; i < size; i++) {
             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            if (internalTypes.contains(consumer.getType())) {
+            if ((consumer.getType() & types) != 0) {
                 consumer.onPerceptible(perceptible);
             }
         }
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 7a498ad..21c0395 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -27,7 +27,6 @@
 import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.getDefaultVisibility;
-import static android.view.InsetsState.toPublicType;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -74,9 +73,9 @@
     }
 
     protected final InsetsController mController;
-    protected boolean mRequestedVisible;
     protected final InsetsState mState;
-    protected final @InternalInsetsType int mType;
+    private final @InternalInsetsType int mInternalType;
+    private final @InsetsType int mType;
 
     private static final String TAG = "InsetsSourceConsumer";
     private final Supplier<Transaction> mTransactionSupplier;
@@ -99,11 +98,11 @@
      */
     public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
             Supplier<Transaction> transactionSupplier, InsetsController controller) {
-        mType = type;
+        mType = InsetsState.toPublicType(type);
+        mInternalType = type;
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mRequestedVisible = getDefaultVisibility(type);
     }
 
     /**
@@ -117,7 +116,7 @@
      */
     public boolean setControl(@Nullable InsetsSourceControl control,
             @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
-        if (mType == ITYPE_IME) {
+        if (mInternalType == ITYPE_IME) {
             ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl",
                     mController.getHost().getInputMethodManager(), null /* icProto */);
         }
@@ -141,9 +140,10 @@
             mController.notifyControlRevoked(this);
 
             // Check if we need to restore server visibility.
-            final InsetsSource source = mState.getSource(mType);
+            final InsetsSource source = mState.getSource(mInternalType);
             final boolean serverVisibility =
-                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType);
+                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(
+                            mInternalType);
             if (source.isVisible() != serverVisibility) {
                 source.setVisible(serverVisibility);
                 mController.notifyVisibilityChanged();
@@ -159,9 +159,9 @@
                 if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
                         mController.getHost().getRootViewTitle(), requestedVisible));
                 if (requestedVisible) {
-                    showTypes[0] |= toPublicType(getType());
+                    showTypes[0] |= mType;
                 } else {
-                    hideTypes[0] |= toPublicType(getType());
+                    hideTypes[0] |= mType;
                 }
             } else {
                 // We are gaining control, but don't need to run an animation.
@@ -172,7 +172,7 @@
 
                 // If we have a new leash, make sure visibility is up-to-date, even though we
                 // didn't want to run an animation above.
-                if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+                if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
                     applyRequestedVisibilityToControl();
                 }
 
@@ -195,29 +195,32 @@
 
     /**
      * Determines if the consumer will be shown after control is available.
-     * Note: for system bars this method is same as {@link #isRequestedVisible()}.
      *
      * @return {@code true} if consumer has a pending show.
      */
     protected boolean isRequestedVisibleAwaitingControl() {
-        return isRequestedVisible();
+        return (mController.getRequestedVisibleTypes() & mType) != 0;
     }
 
-    int getType() {
+    @InsetsType int getType() {
         return mType;
     }
 
+    @InternalInsetsType int getInternalType() {
+        return mInternalType;
+    }
+
     @VisibleForTesting
     public void show(boolean fromIme) {
         if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
-                InsetsState.typeToString(mType), fromIme));
+                InsetsState.typeToString(mInternalType), fromIme));
         setRequestedVisible(true);
     }
 
     @VisibleForTesting
     public void hide() {
         if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
-                InsetsState.typeToString(mType), mController.getHost().getRootViewTitle()));
+                InsetsState.typeToString(mInternalType), mController.getHost().getRootViewTitle()));
         setRequestedVisible(false);
     }
 
@@ -245,11 +248,13 @@
     }
 
     boolean applyLocalVisibilityOverride() {
-        final InsetsSource source = mState.peekSource(mType);
-        final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType);
+        final InsetsSource source = mState.peekSource(mInternalType);
+        final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(
+                mInternalType);
         final boolean hasControl = mSourceControl != null;
+        final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
 
-        if (mType == ITYPE_IME) {
+        if (mInternalType == ITYPE_IME) {
             ImeTracing.getInstance().triggerClientDump(
                     "InsetsSourceConsumer#applyLocalVisibilityOverride",
                     mController.getHost().getInputMethodManager(), null /* icProto */);
@@ -259,23 +264,18 @@
         if (!hasControl) {
             if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
                     + mController.getHost().getRootViewTitle()
-                    + " requestedVisible " + mRequestedVisible);
+                    + " requestedVisible=" + requestedVisible);
             return false;
         }
-        if (isVisible == mRequestedVisible) {
+        if (isVisible == requestedVisible) {
             return false;
         }
         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
-                mController.getHost().getRootViewTitle(), mRequestedVisible));
-        mState.getSource(mType).setVisible(mRequestedVisible);
+                mController.getHost().getRootViewTitle(), requestedVisible));
+        mState.getSource(mInternalType).setVisible(requestedVisible);
         return true;
     }
 
-    @VisibleForTesting
-    public boolean isRequestedVisible() {
-        return mRequestedVisible;
-    }
-
     /**
      * Request to show current window type.
      *
@@ -314,7 +314,7 @@
 
     @VisibleForTesting(visibility = PACKAGE)
     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
-        InsetsSource source = mState.peekSource(mType);
+        InsetsSource source = mState.peekSource(mInternalType);
         if (source == null || animationType == ANIMATION_TYPE_NONE
                 || source.getFrame().equals(newSource.getFrame())) {
             mPendingFrame = null;
@@ -339,7 +339,7 @@
     @VisibleForTesting(visibility = PACKAGE)
     public boolean notifyAnimationFinished() {
         if (mPendingFrame != null) {
-            InsetsSource source = mState.getSource(mType);
+            InsetsSource source = mState.getSource(mInternalType);
             source.setFrame(mPendingFrame);
             source.setVisibleFrame(mPendingVisibleFrame);
             mPendingFrame = null;
@@ -354,11 +354,8 @@
      * the moment.
      */
     protected void setRequestedVisible(boolean requestedVisible) {
-        if (mRequestedVisible != requestedVisible) {
-            mRequestedVisible = requestedVisible;
-            mController.onRequestedVisibilityChanged(this);
-            if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
-        }
+        mController.setRequestedVisibleTypes(requestedVisible ? mType : 0, mType);
+        if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
         if (applyLocalVisibilityOverride()) {
             mController.notifyVisibilityChanged();
         }
@@ -369,25 +366,26 @@
             return;
         }
 
+        final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
         try (Transaction t = mTransactionSupplier.get()) {
-            if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible);
-            if (mRequestedVisible) {
+            if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
+            if (requestedVisible) {
                 t.show(mSourceControl.getLeash());
             } else {
                 t.hide(mSourceControl.getLeash());
             }
             // Ensure the alpha value is aligned with the actual requested visibility.
-            t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0);
+            t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
             t.apply();
         }
-        onPerceptible(mRequestedVisible);
+        onPerceptible(requestedVisible);
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType));
+        proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mInternalType));
         proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
-        proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible);
+        proto.write(IS_REQUESTED_VISIBLE, (mController.getRequestedVisibleTypes() & mType) != 0);
         if (mSourceControl != null) {
             mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
         }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a08a5a8..b3e8fb6 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1757,7 +1757,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that will be in this event.
@@ -1771,7 +1771,7 @@
      * @param buttonState The state of buttons that are pressed.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1779,16 +1779,18 @@
      * @param source The source of this event.
      * @param displayId The display ID associated with this event.
      * @param flags The motion event flags.
+     * @param classification The classification to give this event.
      * @hide
      */
-    static public MotionEvent obtain(long downTime, long eventTime,
+    public static MotionEvent obtain(long downTime, long eventTime,
             int action, int pointerCount, PointerProperties[] pointerProperties,
             PointerCoords[] pointerCoords, int metaState, int buttonState,
             float xPrecision, float yPrecision, int deviceId,
-            int edgeFlags, int source, int displayId, int flags) {
+            int edgeFlags, int source, int displayId, int flags,
+            @Classification int classification) {
         MotionEvent ev = obtain();
         final boolean success = ev.initialize(deviceId, source, displayId, action, flags, edgeFlags,
-                metaState, buttonState, CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
+                metaState, buttonState, classification, 0, 0, xPrecision, yPrecision,
                 downTime * NS_PER_MS, eventTime * NS_PER_MS,
                 pointerCount, pointerProperties, pointerCoords);
         if (!success) {
@@ -1805,7 +1807,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that will be in this event.
@@ -1819,7 +1821,47 @@
      * @param buttonState The state of buttons that are pressed.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
+     * zero indicates that the event didn't come from a physical device; other
+     * numbers are arbitrary and you shouldn't depend on the values.
+     * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+     * MotionEvent.
+     * @param source The source of this event.
+     * @param displayId The display ID associated with this event.
+     * @param flags The motion event flags.
+     * @hide
+     */
+    public static MotionEvent obtain(long downTime, long eventTime,
+            int action, int pointerCount, PointerProperties[] pointerProperties,
+            PointerCoords[] pointerCoords, int metaState, int buttonState,
+            float xPrecision, float yPrecision, int deviceId,
+            int edgeFlags, int source, int displayId, int flags) {
+        return obtain(downTime, eventTime, action, pointerCount, pointerProperties, pointerCoords,
+                metaState, buttonState, xPrecision, yPrecision, deviceId, edgeFlags, source,
+                displayId, flags, CLASSIFICATION_NONE);
+    }
+
+    /**
+     * Create a new MotionEvent, filling in all of the basic values that
+     * define the motion.
+     *
+     * @param downTime The time (in ms) when the user originally pressed down to start
+     * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param eventTime The time (in ms) when this specific event was generated.  This
+     * must be obtained from {@link SystemClock#uptimeMillis()}.
+     * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+     * @param pointerCount The number of pointers that will be in this event.
+     * @param pointerProperties An array of <em>pointerCount</em> values providing
+     * a {@link PointerProperties} property object for each pointer, which must
+     * include the pointer identifier.
+     * @param pointerCoords An array of <em>pointerCount</em> values providing
+     * a {@link PointerCoords} coordinate object for each pointer.
+     * @param metaState The state of any meta / modifier keys that were in effect when
+     * the event was generated.
+     * @param buttonState The state of buttons that are pressed.
+     * @param xPrecision The precision of the X coordinate being reported.
+     * @param yPrecision The precision of the Y coordinate being reported.
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1843,7 +1885,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that will be in this event.
@@ -1855,7 +1897,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1890,7 +1932,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param x The X coordinate of this event.
@@ -1907,7 +1949,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -1927,7 +1969,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param x The X coordinate of this event.
@@ -1944,7 +1986,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param source The source of this event.
@@ -1986,7 +2028,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param pointerCount The number of pointers that are active in this event.
@@ -2004,7 +2046,7 @@
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
      * @param yPrecision The precision of the Y coordinate being reported.
-     * @param deviceId The id for the device that this event came from.  An id of
+     * @param deviceId The ID for the device that this event came from.  An ID of
      * zero indicates that the event didn't come from a physical device; other
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
@@ -2028,7 +2070,7 @@
      *
      * @param downTime The time (in ms) when the user originally pressed down to start
      * a stream of position events.  This must be obtained from {@link SystemClock#uptimeMillis()}.
-     * @param eventTime  The the time (in ms) when this specific event was generated.  This
+     * @param eventTime The time (in ms) when this specific event was generated.  This
      * must be obtained from {@link SystemClock#uptimeMillis()}.
      * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
      * @param x The X coordinate of this event.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 49d9e67..2b071db 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17434,7 +17434,7 @@
      * (but after its own view has been drawn).
      * @param canvas the canvas on which to draw the view
      */
-    protected void dispatchDraw(Canvas canvas) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
 
     }
 
@@ -20719,7 +20719,7 @@
         out.bottom = mScrollY + mBottom - mTop;
     }
 
-    private void onDrawScrollIndicators(Canvas c) {
+    private void onDrawScrollIndicators(@NonNull Canvas c) {
         if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
             // No scroll indicators enabled.
             return;
@@ -20903,7 +20903,7 @@
      *
      * @see #awakenScrollBars(int)
      */
-    protected final void onDrawScrollBars(Canvas canvas) {
+    protected final void onDrawScrollBars(@NonNull Canvas canvas) {
         // scrollbars are drawn only when the animation is running
         final ScrollabilityCache cache = mScrollCache;
 
@@ -21015,7 +21015,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+    protected void onDrawHorizontalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
             int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
@@ -21035,7 +21035,7 @@
      * @hide
      */
     @UnsupportedAppUsage
-    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+    protected void onDrawVerticalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
             int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
@@ -21046,7 +21046,7 @@
      *
      * @param canvas the canvas on which the background will be drawn
      */
-    protected void onDraw(Canvas canvas) {
+    protected void onDraw(@NonNull Canvas canvas) {
     }
 
     /*
@@ -23161,7 +23161,7 @@
      *
      * @hide
      */
-    protected final boolean drawsWithRenderNode(Canvas canvas) {
+    protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) {
         return mAttachInfo != null
                 && mAttachInfo.mHardwareAccelerated
                 && canvas.isHardwareAccelerated();
@@ -23173,7 +23173,7 @@
      * This is where the View specializes rendering behavior based on layer type,
      * and hardware acceleration.
      */
-    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+    boolean draw(@NonNull Canvas canvas, ViewGroup parent, long drawingTime) {
 
         final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
 
@@ -23461,7 +23461,7 @@
         return (int) (dips * scale + 0.5f);
     }
 
-    final private void debugDrawFocus(Canvas canvas) {
+    private void debugDrawFocus(@NonNull Canvas canvas) {
         if (isFocused()) {
             final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
             final int l = mScrollX;
@@ -23496,7 +23496,7 @@
      * @param canvas The Canvas to which the View is rendered.
      */
     @CallSuper
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
         final int privateFlags = mPrivateFlags;
         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 
@@ -23731,7 +23731,7 @@
      * @param canvas Canvas on which to draw the background
      */
     @UnsupportedAppUsage
-    private void drawBackground(Canvas canvas) {
+    private void drawBackground(@NonNull Canvas canvas) {
         final Drawable background = mBackground;
         if (background == null) {
             return;
@@ -24631,7 +24631,7 @@
      * Draw the default focus highlight onto the canvas if there is one and this view is focused.
      * @param canvas the canvas where we're drawing the highlight.
      */
-    private void drawDefaultFocusHighlight(Canvas canvas) {
+    private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
         if (mDefaultFocusHighlight != null && isFocused()) {
             if (mDefaultFocusHighlightSizeChanged) {
                 mDefaultFocusHighlightSizeChanged = false;
@@ -25429,7 +25429,7 @@
      *
      * @param canvas canvas to draw into
      */
-    public void onDrawForeground(Canvas canvas) {
+    public void onDrawForeground(@NonNull Canvas canvas) {
         onDrawScrollIndicators(canvas);
         onDrawScrollBars(canvas);
 
@@ -27487,7 +27487,7 @@
          *
          * @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
          */
-        public void onDrawShadow(Canvas canvas) {
+        public void onDrawShadow(@NonNull Canvas canvas) {
             final View view = mView.get();
             if (view != null) {
                 view.draw(canvas);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c4f20dc..46b2cfc 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4166,7 +4166,7 @@
     /**
      * @hide
      */
-    protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+    protected void onDebugDrawMargins(@NonNull Canvas canvas, Paint paint) {
         for (int i = 0; i < getChildCount(); i++) {
             View c = getChildAt(i);
             c.getLayoutParams().onDebugDraw(c, canvas, paint);
@@ -4176,7 +4176,7 @@
     /**
      * @hide
      */
-    protected void onDebugDraw(Canvas canvas) {
+    protected void onDebugDraw(@NonNull Canvas canvas) {
         Paint paint = getDebugPaint();
 
         // Draw optical bounds
@@ -4224,7 +4224,7 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
         final int childrenCount = mChildrenCount;
         final View[] children = mChildren;
         int flags = mGroupFlags;
@@ -4533,7 +4533,7 @@
      * @param drawingTime The time at which draw is occurring
      * @return True if an invalidate() was issued
      */
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+    protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
         return child.draw(canvas, this, drawingTime);
     }
 
@@ -9208,7 +9208,8 @@
         }
     }
 
-    private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+    private static void drawRect(@NonNull Canvas canvas, Paint paint, int x1, int y1,
+                                 int x2, int y2) {
         if (sDebugLines== null) {
             // TODO: This won't work with multiple UI threads in a single process
             sDebugLines = new float[16];
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d07a797..9dbabab 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -897,6 +897,7 @@
     private CharSequence mTooltipText;
     private String mViewIdResourceName;
     private String mUniqueId;
+    private CharSequence mContainerTitle;
     private ArrayList<String> mExtraDataKeys;
 
     @UnsupportedAppUsage
@@ -3631,6 +3632,47 @@
     }
 
     /**
+     * Sets the container title for app-developer-defined container which can be any type of
+     * ViewGroup or layout.
+     * Container title will be used to group together related controls, similar to HTML fieldset.
+     * Or container title may identify a large piece of the UI that is visibly grouped together,
+     * such as a toolbar or a card, etc.
+     * <p>
+     * Container title helps to assist in navigation across containers and other groups.
+     * For example, a screen reader may use this to determine where to put accessibility focus.
+     * </p>
+     * <p>
+     * Container title is different from pane title{@link #setPaneTitle} which indicates that the
+     * node represents a window or activity.
+     * </p>
+     *
+     * <p>
+     *  Example: An app can set container titles on several non-modal menus, containing TextViews
+     *  or ImageButtons that have content descriptions, text, etc. Screen readers can quickly
+     *  switch accessibility focus among menus instead of child views.  Other accessibility-services
+     *  can easily find the menu.
+     * </p>
+     *
+     * @param containerTitle The container title that is associated with a ViewGroup/Layout on the
+     *                       screen.
+     */
+    public void setContainerTitle(@Nullable CharSequence containerTitle) {
+        enforceNotSealed();
+        mContainerTitle = (containerTitle == null) ? null
+                : containerTitle.subSequence(0, containerTitle.length());
+    }
+
+    /**
+     * Returns the container title.
+     *
+     * @see #setContainerTitle for details.
+     */
+    @Nullable
+    public CharSequence getContainerTitle() {
+        return mContainerTitle;
+    }
+
+    /**
      * Sets the token and node id of the leashed parent.
      *
      * @param token The token.
@@ -3963,6 +4005,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mContainerTitle, DEFAULT.mContainerTitle)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -4108,10 +4154,10 @@
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mContainerTitle);
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mUniqueId);
-
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionEnd);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mInputType);
@@ -4204,6 +4250,7 @@
         mContentDescription = other.mContentDescription;
         mPaneTitle = other.mPaneTitle;
         mTooltipText = other.mTooltipText;
+        mContainerTitle = other.mContainerTitle;
         mViewIdResourceName = other.mViewIdResourceName;
 
         if (mActions != null) mActions.clear();
@@ -4347,6 +4394,7 @@
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mContainerTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mUniqueId = parcel.readString();
 
@@ -4675,6 +4723,7 @@
         builder.append("; stateDescription: ").append(mStateDescription);
         builder.append("; contentDescription: ").append(mContentDescription);
         builder.append("; tooltipText: ").append(mTooltipText);
+        builder.append("; containerTitle: ").append(mContainerTitle);
         builder.append("; viewIdResName: ").append(mViewIdResourceName);
         builder.append("; uniqueId: ").append(mUniqueId);
 
diff --git a/core/java/android/view/inputmethod/DeleteGesture.java b/core/java/android/view/inputmethod/DeleteGesture.java
index c88158f..b843594 100644
--- a/core/java/android/view/inputmethod/DeleteGesture.java
+++ b/core/java/android/view/inputmethod/DeleteGesture.java
@@ -33,7 +33,7 @@
  * <p>Note: This deletes all text <em>within</em> the given area. To delete a range <em>between</em>
  * two areas, use {@link DeleteRangeGesture}.</p>
  */
-public final class DeleteGesture extends HandwritingGesture implements Parcelable {
+public final class DeleteGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mArea;
diff --git a/core/java/android/view/inputmethod/DeleteRangeGesture.java b/core/java/android/view/inputmethod/DeleteRangeGesture.java
index 53b4209..0bce15e 100644
--- a/core/java/android/view/inputmethod/DeleteRangeGesture.java
+++ b/core/java/android/view/inputmethod/DeleteRangeGesture.java
@@ -34,7 +34,7 @@
  * <p>Note: this deletes text within a range <em>between</em> two given areas. To delete all text
  * <em>within</em> a single area, use {@link DeleteGesture}.</p>
  */
-public final class DeleteRangeGesture extends HandwritingGesture implements Parcelable {
+public final class DeleteRangeGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mStartArea;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index febdac2..9ebaa67 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -55,8 +55,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * An EditorInfo describes several attributes of a text editing object
@@ -534,6 +536,8 @@
 
     private @HandwritingGesture.GestureTypeFlags int mSupportedHandwritingGestureTypes;
 
+    private @HandwritingGesture.GestureTypeFlags int mSupportedHandwritingGesturePreviewTypes;
+
     /**
      * Set the Handwriting gestures supported by the current {@code Editor}.
      * For an editor that supports Stylus Handwriting
@@ -541,8 +545,11 @@
      * supported gestures.
      * <p> If editor doesn't support one of the declared types, IME will not send those Gestures
      *  to the editor. Instead they will fallback to using normal text input. </p>
+     * <p>Note: A supported gesture may not have preview supported
+     * {@link #getSupportedHandwritingGesturePreviews()}.</p>
      * @param gestures List of supported gesture classes including any of {@link SelectGesture},
      * {@link InsertGesture}, {@link DeleteGesture}.
+     * @see #setSupportedHandwritingGesturePreviews(Set)
      */
     public void setSupportedHandwritingGestures(
             @NonNull List<Class<? extends HandwritingGesture>> gestures) {
@@ -584,6 +591,7 @@
      * {@link InputMethodManager#startStylusHandwriting}, it also declares supported gestures.
      * @return List of supported gesture classes including any of {@link SelectGesture},
      * {@link InsertGesture}, {@link DeleteGesture}.
+     * @see #getSupportedHandwritingGesturePreviews()
      */
     @NonNull
     public List<Class<? extends HandwritingGesture>> getSupportedHandwritingGestures() {
@@ -623,6 +631,86 @@
     }
 
     /**
+     * Set the Handwriting gesture previews supported by the current {@code Editor}.
+     * For an editor that supports Stylus Handwriting
+     * {@link InputMethodManager#startStylusHandwriting}, it is also recommended that it declares
+     * supported gesture previews.
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} may not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
+     * <p> If editor doesn't support one of the declared types, gesture preview will be ignored.</p>
+     * @param gestures Set of supported gesture classes. One of {@link SelectGesture},
+     * {@link SelectRangeGesture}, {@link DeleteGesture}, {@link DeleteRangeGesture}.
+     * @see #setSupportedHandwritingGestures(List)
+     */
+    public void setSupportedHandwritingGesturePreviews(
+            @NonNull Set<Class<? extends PreviewableHandwritingGesture>> gestures) {
+        Objects.requireNonNull(gestures);
+        if (gestures.isEmpty()) {
+            mSupportedHandwritingGesturePreviewTypes = 0;
+            return;
+        }
+
+        int supportedTypes = 0;
+        for (Class<? extends PreviewableHandwritingGesture> gesture : gestures) {
+            Objects.requireNonNull(gesture);
+            if (gesture.equals(SelectGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT;
+            } else if (gesture.equals(SelectRangeGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT_RANGE;
+            } else if (gesture.equals(DeleteGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE;
+            } else if (gesture.equals(DeleteRangeGesture.class)) {
+                supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE_RANGE;
+            } else {
+                throw new IllegalArgumentException(
+                        "Unsupported gesture type for preview: " + gesture);
+            }
+        }
+
+        mSupportedHandwritingGesturePreviewTypes = supportedTypes;
+    }
+
+    /**
+     * Returns the combination of Stylus handwriting gesture preview types
+     * supported by the current {@code Editor}.
+     * For an editor that supports Stylus Handwriting.
+     * {@link InputMethodManager#startStylusHandwriting}, it also declares supported gesture
+     * previews.
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} may not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
+     * @return Set of supported gesture preview classes. One of {@link SelectGesture},
+     * {@link SelectRangeGesture}, {@link DeleteGesture}, {@link DeleteRangeGesture}.
+     * @see #getSupportedHandwritingGestures()
+     */
+    @NonNull
+    public Set<Class<? extends PreviewableHandwritingGesture>>
+            getSupportedHandwritingGesturePreviews() {
+        Set<Class<? extends PreviewableHandwritingGesture>> set  = new HashSet<>();
+        if (mSupportedHandwritingGesturePreviewTypes == 0) {
+            return set;
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes & HandwritingGesture.GESTURE_TYPE_SELECT)
+                == HandwritingGesture.GESTURE_TYPE_SELECT) {
+            set.add(SelectGesture.class);
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes
+                & HandwritingGesture.GESTURE_TYPE_SELECT_RANGE)
+                        == HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) {
+            set.add(SelectRangeGesture.class);
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes & HandwritingGesture.GESTURE_TYPE_DELETE)
+                == HandwritingGesture.GESTURE_TYPE_DELETE) {
+            set.add(DeleteGesture.class);
+        }
+        if ((mSupportedHandwritingGesturePreviewTypes
+                & HandwritingGesture.GESTURE_TYPE_DELETE_RANGE)
+                        == HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) {
+            set.add(DeleteRangeGesture.class);
+        }
+        return set;
+    }
+
+    /**
      * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
      * matter what user ID the calling process has.
      *
@@ -1114,6 +1202,9 @@
         pw.println(prefix + "supportedHandwritingGestureTypes="
                 + InputMethodDebug.handwritingGestureTypeFlagsToString(
                         mSupportedHandwritingGestureTypes));
+        pw.println(prefix + "supportedHandwritingGesturePreviewTypes="
+                + InputMethodDebug.handwritingGestureTypeFlagsToString(
+                        mSupportedHandwritingGesturePreviewTypes));
         pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
         if (targetInputMethodUser != null) {
             pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
@@ -1149,6 +1240,8 @@
         newEditorInfo.contentMimeTypes = ArrayUtils.cloneOrNull(contentMimeTypes);
         newEditorInfo.targetInputMethodUser = targetInputMethodUser;
         newEditorInfo.mSupportedHandwritingGestureTypes = mSupportedHandwritingGestureTypes;
+        newEditorInfo.mSupportedHandwritingGesturePreviewTypes =
+                mSupportedHandwritingGesturePreviewTypes;
         return newEditorInfo;
     }
 
@@ -1177,6 +1270,7 @@
         dest.writeString(fieldName);
         dest.writeBundle(extras);
         dest.writeInt(mSupportedHandwritingGestureTypes);
+        dest.writeInt(mSupportedHandwritingGesturePreviewTypes);
         dest.writeBoolean(mInitialSurroundingText != null);
         if (mInitialSurroundingText != null) {
             mInitialSurroundingText.writeToParcel(dest, flags);
@@ -1215,6 +1309,7 @@
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
                     res.mSupportedHandwritingGestureTypes = source.readInt();
+                    res.mSupportedHandwritingGesturePreviewTypes = source.readInt();
                     boolean hasInitialSurroundingText = source.readBoolean();
                     if (hasInitialSurroundingText) {
                         res.mInitialSurroundingText =
@@ -1258,6 +1353,8 @@
                 && initialCapsMode == that.initialCapsMode
                 && fieldId == that.fieldId
                 && mSupportedHandwritingGestureTypes == that.mSupportedHandwritingGestureTypes
+                && mSupportedHandwritingGesturePreviewTypes
+                        == that.mSupportedHandwritingGesturePreviewTypes
                 && Objects.equals(autofillId, that.autofillId)
                 && Objects.equals(privateImeOptions, that.privateImeOptions)
                 && Objects.equals(packageName, that.packageName)
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index e8b1b46..a66c67b 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -346,14 +346,13 @@
 
     @AnyThread
     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
-    static void showInputMethodPickerFromSystem(@NonNull IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId) {
+    static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return;
         }
         try {
-            service.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+            service.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 2099a31..c94a372 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -26,6 +26,7 @@
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.text.TextUtils;
 import android.view.KeyCharacterMap;
@@ -1031,6 +1032,8 @@
     /**
      * Perform a handwriting gesture on text.
      *
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} may not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
      * @param gesture the gesture to perform
      * @param executor The executor to run the callback on.
      * @param consumer if the caller passes a non-null consumer, the editor must invoke this
@@ -1041,6 +1044,7 @@
      * completed. Will be invoked on the given {@link Executor}.
      * Default implementation provides a callback to {@link IntConsumer} with
      * {@link #HANDWRITING_GESTURE_RESULT_UNSUPPORTED}.
+     * @see #previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
      */
     default void performHandwritingGesture(
             @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@@ -1051,6 +1055,31 @@
     }
 
     /**
+     * Preview a handwriting gesture on text.
+     * Provides a real-time preview for a gesture to user for an ongoing gesture. e.g. as user
+     * begins to draw a circle around text, resulting selection {@link SelectGesture} is previewed
+     * while stylus is moving over applicable text.
+     *
+     * <p>Note: A supported gesture {@link EditorInfo#getSupportedHandwritingGestures()} might not
+     * have preview supported {@link EditorInfo#getSupportedHandwritingGesturePreviews()}.</p>
+     * @param gesture the gesture to preview. Preview support for a gesture (regardless of whether
+     *  implemented by editor) can be determined if gesture subclasses
+     *  {@link PreviewableHandwritingGesture}. Supported previewable gestures include
+     *  {@link SelectGesture}, {@link SelectRangeGesture}, {@link DeleteGesture} and
+     *  {@link DeleteRangeGesture}.
+     * @param cancellationSignal signal to cancel an ongoing preview.
+     * @return true on successfully sending command to Editor, false if not implemented by editor or
+     * the input connection is no longer valid or preview was cancelled with
+     * {@link CancellationSignal}.
+     * @see #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
+     */
+    default boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return false;
+    }
+
+    /**
      * The editor is requested to call
      * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
      * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 7af96b6..4befd6f 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.view.KeyEvent;
 
@@ -340,6 +341,17 @@
      * @throws NullPointerException if the target is {@code null}.
      */
     @Override
+    public boolean previewHandwritingGesture(
+            @NonNull PreviewableHandwritingGesture gesture,
+            @Nullable CancellationSignal cancellationSignal) {
+        return mTarget.previewHandwritingGesture(gesture, cancellationSignal);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
     public boolean requestCursorUpdates(int cursorUpdateMode) {
         return mTarget.requestCursorUpdates(cursorUpdateMode);
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 91df4f5d..74afced 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -3115,6 +3115,8 @@
      * when it was started, which allows it to perform this operation on
      * itself.
      * @param id The unique identifier for the new input method to be switched to.
+     * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
+     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
      * @deprecated Use {@link InputMethodService#switchInputMethod(String)}
      * instead. This method was intended for IME developers who should be accessing APIs through
      * the service. APIs in this class are intended for app developers interacting with the IME.
@@ -3185,6 +3187,8 @@
      * itself.
      * @param id The unique identifier for the new input method to be switched to.
      * @param subtype The new subtype of the new input method to be switched to.
+     * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
+     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
      * @deprecated Use
      * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}
      * instead. This method was intended for IME developers who should be accessing APIs through
@@ -3463,7 +3467,7 @@
         final int mode = showAuxiliarySubtypes
                 ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
                 : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
-        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
+        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
     }
 
     @GuardedBy("mH")
diff --git a/core/java/android/view/inputmethod/PreviewableHandwritingGesture.java b/core/java/android/view/inputmethod/PreviewableHandwritingGesture.java
new file mode 100644
index 0000000..7683ece
--- /dev/null
+++ b/core/java/android/view/inputmethod/PreviewableHandwritingGesture.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.os.CancellationSignal;
+
+import java.util.Set;
+
+/**
+ * A {@link HandwritingGesture} that can be
+ * {@link InputConnection#previewHandwritingGesture(
+ *  PreviewableHandwritingGesture, CancellationSignal) previewed}.
+ *
+ *  Note: An editor might only implement a subset of gesture previews and declares the supported
+ *  ones via {@link EditorInfo#getSupportedHandwritingGesturePreviews}.
+ *
+ * @see EditorInfo#setSupportedHandwritingGesturePreviews(Set)
+ * @see EditorInfo#getSupportedHandwritingGesturePreviews()
+ */
+public abstract class PreviewableHandwritingGesture extends HandwritingGesture {
+    PreviewableHandwritingGesture() {
+        // intentionally empty.
+    }
+}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index a927765..7525d72 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -30,7 +30,9 @@
 import android.annotation.Nullable;
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
+import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.ResultReceiver;
 import android.os.Trace;
@@ -1015,24 +1017,31 @@
         });
     }
 
-    /**
-     * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
-     *
-     * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
-     * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
-     * @param cursorUpdateFilter the filter for
-     *      {@link InputConnection#requestCursorUpdates(int, int)}
-     * @param imeDisplayId displayId on which IME is displayed.
-     */
     @Dispatching(cancellable = true)
-    public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
-            int imeDisplayId) {
-        final int currentSessionId = mCurrentSessionId.get();
-        dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
-            if (currentSessionId != mCurrentSessionId.get()) {
+    @Override
+    public void previewHandwritingGesture(
+            InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
+            ICancellationSignal transport) {
+
+        // TODO(b/254727073): Implement CancellationSignal receiver
+        final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
+        // Previews always use PreviewableHandwritingGesture but if incorrectly wrong class is
+        // passed, ClassCastException will be sent back to caller.
+        final PreviewableHandwritingGesture gesture =
+                (PreviewableHandwritingGesture) gestureContainer.get();
+
+        dispatchWithTracing("previewHandwritingGesture", () -> {
+            if (header.mSessionId != mCurrentSessionId.get()
+                    || (cancellationSignal != null && cancellationSignal.isCanceled())) {
                 return;  // cancelled
             }
-            requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
+            InputConnection ic = getInputConnection();
+            if (ic == null || !isActive()) {
+                Log.w(TAG, "previewHandwritingGesture on inactive InputConnection");
+                return; // cancelled
+            }
+
+            ic.previewHandwritingGesture(gesture, cancellationSignal);
         });
     }
 
diff --git a/core/java/android/view/inputmethod/SelectGesture.java b/core/java/android/view/inputmethod/SelectGesture.java
index 6dc4ed2..ba600df 100644
--- a/core/java/android/view/inputmethod/SelectGesture.java
+++ b/core/java/android/view/inputmethod/SelectGesture.java
@@ -33,7 +33,7 @@
  * <p>Note: This selects all text <em>within</em> the given area. To select a range <em>between</em>
  * two areas, use {@link SelectRangeGesture}.</p>
  */
-public final class SelectGesture extends HandwritingGesture implements Parcelable {
+public final class SelectGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mArea;
@@ -72,7 +72,6 @@
         return mArea;
     }
 
-
     /**
      * Builder for {@link SelectGesture}. This class is not designed to be thread-safe.
      */
diff --git a/core/java/android/view/inputmethod/SelectRangeGesture.java b/core/java/android/view/inputmethod/SelectRangeGesture.java
index 7cb6002..c31bc27 100644
--- a/core/java/android/view/inputmethod/SelectRangeGesture.java
+++ b/core/java/android/view/inputmethod/SelectRangeGesture.java
@@ -34,7 +34,7 @@
  * <p>Note: this selects text within a range <em>between</em> two given areas. To select all text
  * <em>within</em> a single area, use {@link SelectGesture}</p>
  */
-public final class SelectRangeGesture extends HandwritingGesture implements Parcelable {
+public final class SelectRangeGesture extends PreviewableHandwritingGesture implements Parcelable {
 
     private @Granularity int mGranularity;
     private RectF mStartArea;
@@ -87,7 +87,6 @@
         return mEndArea;
     }
 
-
     /**
      * Builder for {@link SelectRangeGesture}. This class is not designed to be thread-safe.
      */
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5e74381..510a92d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -116,9 +116,7 @@
  * <p class="note">ListView attempts to reuse view objects in order to improve performance and
  * avoid a lag in response to user scrolls.  To take advantage of this feature, check if the
  * {@code convertView} provided to {@code getView(...)} is null before creating or inflating a new
- * view object.  See
- * <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
- * Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
+ * view object.</p>
  *
  * <p>To specify an action when a user clicks or taps on a single list item, see
  * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 36eaf49..57e0ce8 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -105,4 +105,7 @@
 
     /** @return An interface enabling the transition players to report its metrics. */
     ITransitionMetricsReporter getTransitionMetricsReporter();
+
+    /** @return The transaction queue token used by WM. */
+    IBinder getApplyToken();
 }
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2a80d02..930aaa2 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -26,6 +26,7 @@
 import android.os.RemoteException;
 import android.util.Singleton;
 import android.view.RemoteAnimationAdapter;
+import android.view.SurfaceControl;
 
 /**
  * Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -184,6 +185,26 @@
         }
     }
 
+    /**
+     * Use WM's transaction-queue instead of Shell's independent one. This is necessary
+     * if WM and Shell need to coordinate transactions (eg. for shell transitions).
+     * @return true if successful, false otherwise.
+     * @hide
+     */
+    public boolean shareTransactionQueue() {
+        final IBinder wmApplyToken;
+        try {
+            wmApplyToken = getWindowOrganizerController().getApplyToken();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (wmApplyToken == null) {
+            return false;
+        }
+        SurfaceControl.Transaction.setDefaultApplyToken(wmApplyToken);
+        return true;
+    }
+
     static IWindowOrganizerController getWindowOrganizerController() {
         return IWindowOrganizerControllerSingleton.get();
     }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 822393f..e926605 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1098,6 +1098,9 @@
     @Override // ResolverListCommunicator
     public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
             boolean rebuildCompleted) {
+        if (isDestroyed()) {
+            return;
+        }
         if (isAutolaunching()) {
             return;
         }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 44997b4..0f64f6d 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -556,6 +556,11 @@
             "show_stop_button_for_user_allowlisted_apps";
 
     /**
+     * (boolean) Whether to show notification volume control slider separate from ring.
+     */
+    public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
+
+    /**
      * (boolean) Whether the clipboard overlay is enabled.
      */
     public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
new file mode 100644
index 0000000..7571073
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/Counter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.expresslog;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Counter encapsulates StatsD write API calls */
+public final class Counter {
+
+    // Not instantiable.
+    private Counter() {}
+
+    /**
+     * Increments Telemetry Express Counter metric by 1
+     * @hide
+     */
+    public static void logIncrement(@NonNull String metricId) {
+        logIncrement(metricId, 1);
+    }
+
+    /**
+     * Increments Telemetry Express Counter metric by arbitrary value
+     * @hide
+     */
+    public static void logIncrement(@NonNull String metricId, long amount) {
+        final long metricIdHash = hashString(metricId);
+        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
+    }
+
+    private static native long hashString(String stringToHash);
+}
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index 81e060d..65016c2 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -18,6 +18,7 @@
 
 import android.graphics.RectF;
 import android.os.Bundle;
+import android.os.ICancellationSignal;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -92,6 +93,9 @@
     void performHandwritingGesture(in InputConnectionCommandHeader header,
             in ParcelableHandwritingGesture gesture, in ResultReceiver resultReceiver);
 
+    void previewHandwritingGesture(in InputConnectionCommandHeader header,
+            in ParcelableHandwritingGesture gesture, in ICancellationSignal transport);
+
     void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);
 
     void setComposingRegionWithTextAttribute(in InputConnectionCommandHeader header, int start,
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
index 2a242a5..edd74f6 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
@@ -138,7 +138,7 @@
     private void writeTracesToFilesLocked() {
         try {
             long timeOffsetNs =
-                    TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
                     - SystemClock.elapsedRealtimeNanos();
 
             ProtoOutputStream clientsProto = new ProtoOutputStream();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
similarity index 80%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
index 1550ab3..18bd6e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.inputmethod;
 
-parcelable RemoteTransitionCompat;
+parcelable InputMethodSubtypeHandle;
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..780c637
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.security.InvalidParameterException;
+import java.util.Objects;
+
+/**
+ * A stable and serializable identifier for the pair of {@link InputMethodInfo#getId()} and
+ * {@link android.view.inputmethod.InputMethodSubtype}.
+ *
+ * <p>To save {@link InputMethodSubtypeHandle} to storage, call {@link #toStringHandle()} to get a
+ * {@link String} handle and just save it.  Once you load a {@link String} handle, you can obtain a
+ * {@link InputMethodSubtypeHandle} instance from {@link #of(String)}.</p>
+ *
+ * <p>For better readability, consider specifying {@link RawHandle} annotation to {@link String}
+ * object when it is a raw {@link String} handle.</p>
+ */
+public final class InputMethodSubtypeHandle implements Parcelable {
+    private static final String SUBTYPE_TAG = "subtype";
+    private static final char DATA_SEPARATOR = ':';
+
+    /**
+     * Can be used to annotate {@link String} object if it is raw handle format.
+     */
+    @Retention(SOURCE)
+    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
+            ElementType.PARAMETER})
+    public @interface RawHandle {
+    }
+
+    /**
+     * The main content of this {@link InputMethodSubtypeHandle}.  Is designed to be safe to be
+     * saved into storage.
+     */
+    @RawHandle
+    private final String mHandle;
+
+    /**
+     * Encode {@link InputMethodInfo} and {@link InputMethodSubtype#hashCode()} into
+     * {@link RawHandle}.
+     *
+     * @param imeId {@link InputMethodInfo#getId()} to be used.
+     * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be used.
+     * @return The encoded {@link RawHandle} string.
+     */
+    @AnyThread
+    @RawHandle
+    @NonNull
+    private static String encodeHandle(@NonNull String imeId, int subtypeHashCode) {
+        return imeId + DATA_SEPARATOR + SUBTYPE_TAG + DATA_SEPARATOR + subtypeHashCode;
+    }
+
+    private InputMethodSubtypeHandle(@NonNull String handle) {
+        mHandle = handle;
+    }
+
+    /**
+     * Creates {@link InputMethodSubtypeHandle} from {@link InputMethodInfo} and
+     * {@link InputMethodSubtype}.
+     *
+     * @param imi {@link InputMethodInfo} to be used.
+     * @param subtype {@link InputMethodSubtype} to be used.
+     * @return A {@link InputMethodSubtypeHandle} object.
+     */
+    @AnyThread
+    @NonNull
+    public static InputMethodSubtypeHandle of(
+            @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+        final int subtypeHashCode =
+                subtype != null ? subtype.hashCode() : InputMethodSubtype.SUBTYPE_ID_NONE;
+        return new InputMethodSubtypeHandle(encodeHandle(imi.getId(), subtypeHashCode));
+    }
+
+    /**
+     * Creates {@link InputMethodSubtypeHandle} from a {@link RawHandle} {@link String}, which can
+     * be obtained by {@link #toStringHandle()}.
+     *
+     * @param stringHandle {@link RawHandle} {@link String} to be parsed.
+     * @return A {@link InputMethodSubtypeHandle} object.
+     * @throws NullPointerException when {@code stringHandle} is {@code null}
+     * @throws InvalidParameterException when {@code stringHandle} is not a valid {@link RawHandle}.
+     */
+    @AnyThread
+    @NonNull
+    public static InputMethodSubtypeHandle of(@RawHandle @NonNull String stringHandle) {
+        final SimpleStringSplitter splitter = new SimpleStringSplitter(DATA_SEPARATOR);
+        splitter.setString(Objects.requireNonNull(stringHandle));
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String imeId = splitter.next();
+        final ComponentName componentName = ComponentName.unflattenFromString(imeId);
+        if (componentName == null) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        // TODO: Consolidate IME ID validation logic into one place.
+        if (!Objects.equals(componentName.flattenToShortString(), imeId)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String source = splitter.next();
+        if (!Objects.equals(source, SUBTYPE_TAG)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String hashCodeStr = splitter.next();
+        if (splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final int subtypeHashCode;
+        try {
+            subtypeHashCode = Integer.parseInt(hashCodeStr);
+        } catch (NumberFormatException ignore) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+
+        // Redundant expressions (e.g. "0001" instead of "1") are not allowed.
+        if (!Objects.equals(encodeHandle(imeId, subtypeHashCode), stringHandle)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+
+        return new InputMethodSubtypeHandle(stringHandle);
+    }
+
+    /**
+     * @return {@link ComponentName} of the input method.
+     * @see InputMethodInfo#getComponent()
+     */
+    @AnyThread
+    @NonNull
+    public ComponentName getComponentName() {
+        return ComponentName.unflattenFromString(getImeId());
+    }
+
+    /**
+     * @return IME ID.
+     * @see InputMethodInfo#getId()
+     */
+    @AnyThread
+    @NonNull
+    public String getImeId() {
+        return mHandle.substring(0, mHandle.indexOf(DATA_SEPARATOR));
+    }
+
+    /**
+     * @return {@link RawHandle} {@link String} data that should be stable and persistable.
+     * @see #of(String)
+     */
+    @RawHandle
+    @AnyThread
+    @NonNull
+    public String toStringHandle() {
+        return mHandle;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof InputMethodSubtypeHandle)) {
+            return false;
+        }
+        final InputMethodSubtypeHandle that = (InputMethodSubtypeHandle) obj;
+        return Objects.equals(mHandle, that.mHandle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mHandle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @NonNull
+    @Override
+    public String toString() {
+        return "InputMethodSubtypeHandle{mHandle=" + mHandle + "}";
+    }
+
+    /**
+     * {@link Creator} for parcelable.
+     */
+    public static final Creator<InputMethodSubtypeHandle> CREATOR = new Creator<>() {
+        @Override
+        public InputMethodSubtypeHandle createFromParcel(Parcel in) {
+            return of(in.readString8());
+        }
+
+        @Override
+        public InputMethodSubtypeHandle[] newArray(int size) {
+            return new InputMethodSubtypeHandle[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(toStringHandle());
+    }
+}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9..6870d09 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -51,6 +51,15 @@
     // ------ ro.fw.* ------------ //
     public static final boolean FW_SYSTEM_USER_SPLIT =
             SystemProperties.getBoolean("ro.fw.system_user_split", false);
+    /**
+     * Indicates whether the device should run in headless system user mode,
+     *   in which user 0 only runs the system, not a real user.
+     * <p>WARNING about changing this value during an non-wiping update (OTA):
+     *   <li>If this value is modified via an update, the change will have no effect, since an
+     *       already-existing system user cannot change its mode.
+     *   <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to
+     *       do so will corrupt the system user.
+     */
     public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
             SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 5b5b15f..edbdc86 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -29,7 +29,6 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.service.notification.StatusBarNotification;
-import android.view.InsetsVisibilities;
 
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -201,13 +200,13 @@
      *                         stacks.
      * @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
      * @param behavior the behavior of the focused window.
-     * @param requestedVisibilities the collection of the requested visibilities of system insets.
+     * @param requestedVisibleTypes the collection of insets types requested visible.
      * @param packageName the package name of the focused app.
      * @param letterboxDetails a set of letterbox details of apps visible on the screen.
      */
     void onSystemBarAttributesChanged(int displayId, int appearance,
             in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            int behavior, in InsetsVisibilities requestedVisibilities, String packageName,
+            int behavior, int requestedVisibleTypes, String packageName,
             in LetterboxDetails[] letterboxDetails);
 
     /**
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8b898f0..54221ce 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,7 +21,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
 
 import com.android.internal.view.AppearanceRegion;
 
@@ -40,7 +39,7 @@
     public final IBinder mImeToken;
     public final boolean mNavbarColorManagedByIme;
     public final int mBehavior;
-    public final InsetsVisibilities mRequestedVisibilities;
+    public final int mRequestedVisibleTypes;
     public final String mPackageName;
     public final int[] mTransientBarTypes;
     public final LetterboxDetails[] mLetterboxDetails;
@@ -48,7 +47,7 @@
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
             int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
             int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
-            boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
+            boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
             String packageName, @NonNull int[] transientBarTypes,
             LetterboxDetails[] letterboxDetails) {
         mIcons = new ArrayMap<>(icons);
@@ -62,7 +61,7 @@
         mImeToken = imeToken;
         mNavbarColorManagedByIme = navbarColorManagedByIme;
         mBehavior = behavior;
-        mRequestedVisibilities = requestedVisibilities;
+        mRequestedVisibleTypes = requestedVisibleTypes;
         mPackageName = packageName;
         mTransientBarTypes = transientBarTypes;
         mLetterboxDetails = letterboxDetails;
@@ -86,7 +85,7 @@
         dest.writeStrongBinder(mImeToken);
         dest.writeBoolean(mNavbarColorManagedByIme);
         dest.writeInt(mBehavior);
-        dest.writeTypedObject(mRequestedVisibilities, 0);
+        dest.writeInt(mRequestedVisibleTypes);
         dest.writeString(mPackageName);
         dest.writeIntArray(mTransientBarTypes);
         dest.writeParcelableArray(mLetterboxDetails, flags);
@@ -112,8 +111,7 @@
                     final IBinder imeToken = source.readStrongBinder();
                     final boolean navbarColorManagedByIme = source.readBoolean();
                     final int behavior = source.readInt();
-                    final InsetsVisibilities requestedVisibilities =
-                            source.readTypedObject(InsetsVisibilities.CREATOR);
+                    final int requestedVisibleTypes = source.readInt();
                     final String packageName = source.readString();
                     final int[] transientBarTypes = source.createIntArray();
                     final LetterboxDetails[] letterboxDetails =
@@ -121,7 +119,7 @@
                     return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
                             appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
                             disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
-                            requestedVisibilities, packageName, transientBarTypes,
+                            requestedVisibleTypes, packageName, transientBarTypes,
                             letterboxDetails);
                 }
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index f7bb16e..40c6a05 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -81,8 +81,7 @@
     @EnforcePermission("WRITE_SECURE_SETTINGS")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    void showInputMethodPickerFromSystem(in IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId);
+    void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
 
     @EnforcePermission("TEST_INPUT_METHOD")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c50abb3..cc3d906 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -34,6 +34,8 @@
         "-Wno-error=deprecated-declarations",
         "-Wunused",
         "-Wunreachable-code",
+
+        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
     ],
 
     cppflags: ["-Wno-conversion-null"],
@@ -212,6 +214,7 @@
                 "android_content_res_ResourceTimer.cpp",
                 "android_security_Scrypt.cpp",
                 "com_android_internal_content_om_OverlayConfig.cpp",
+                "com_android_internal_expresslog_Counter.cpp",
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
@@ -247,6 +250,7 @@
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
+                "libtextclassifier_hash_static",
             ],
 
             shared_libs: [
@@ -329,6 +333,7 @@
             header_libs: [
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
+                "libtextclassifier_hash_headers",
             ],
         },
         host: {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9da28a3..d9ca16e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -194,6 +194,7 @@
 extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
+extern int register_com_android_internal_expresslog_Counter(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1575,6 +1576,7 @@
         REG_JNI(register_android_os_SharedMemory),
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
+        REG_JNI(register_com_android_internal_expresslog_Counter),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 9f88f33..01837f4 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -25,6 +25,7 @@
 #include <inttypes.h>
 #include <mutex>
 #include <stdio.h>
+#include <string>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -880,7 +881,7 @@
         case FAILED_TRANSACTION: {
             ALOGE("!!! FAILED BINDER TRANSACTION !!!  (parcel size = %d)", parcelSize);
             const char* exceptionToThrow;
-            char msg[128];
+            std::string msg;
             // TransactionTooLargeException is a checked exception, only throw from certain methods.
             // TODO(b/28321379): Transaction size is the most common cause for FAILED_TRANSACTION
             //        but it is not the only one.  The Binder driver can return BR_FAILED_REPLY
@@ -890,7 +891,7 @@
             if (canThrowRemoteException && parcelSize > 200*1024) {
                 // bona fide large payload
                 exceptionToThrow = "android/os/TransactionTooLargeException";
-                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
+                msg = base::StringPrintf("data parcel size %d bytes", parcelSize);
             } else {
                 // Heuristic: a payload smaller than this threshold "shouldn't" be too
                 // big, so it's probably some other, more subtle problem.  In practice
@@ -899,11 +900,10 @@
                 exceptionToThrow = (canThrowRemoteException)
                         ? "android/os/DeadObjectException"
                         : "java/lang/RuntimeException";
-                snprintf(msg, sizeof(msg) - 1,
-                         "Transaction failed on small parcel; remote process probably died, but "
-                         "this could also be caused by running out of binder buffer space");
+                msg = "Transaction failed on small parcel; remote process probably died, but "
+                      "this could also be caused by running out of binder buffer space";
             }
-            jniThrowException(env, exceptionToThrow, msg);
+            jniThrowException(env, exceptionToThrow, msg.c_str());
         } break;
         case FDS_NOT_ALLOWED:
             jniThrowException(env, "java/lang/RuntimeException",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 8fd0401..eaec58b 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1202,66 +1202,40 @@
     return object;
 }
 
-struct RefreshRateRange {
-    const float min;
-    const float max;
-
-    RefreshRateRange(float min, float max) : min(min), max(max) {}
-
-    RefreshRateRange(JNIEnv* env, jobject obj)
-          : min(env->GetFloatField(obj, gRefreshRateRangeClassInfo.min)),
-            max(env->GetFloatField(obj, gRefreshRateRangeClassInfo.max)) {}
-
-    jobject toJava(JNIEnv* env) const {
-        return env->NewObject(gRefreshRateRangeClassInfo.clazz, gRefreshRateRangeClassInfo.ctor,
-                              min, max);
-    }
-};
-
-struct RefreshRateRanges {
-    const RefreshRateRange physical;
-    const RefreshRateRange render;
-
-    RefreshRateRanges(RefreshRateRange physical, RefreshRateRange render)
-          : physical(physical), render(render) {}
-
-    RefreshRateRanges(JNIEnv* env, jobject obj)
-          : physical(env, env->GetObjectField(obj, gRefreshRateRangesClassInfo.physical)),
-            render(env, env->GetObjectField(obj, gRefreshRateRangesClassInfo.render)) {}
-
-    jobject toJava(JNIEnv* env) const {
-        return env->NewObject(gRefreshRateRangesClassInfo.clazz, gRefreshRateRangesClassInfo.ctor,
-                              physical.toJava(env), render.toJava(env));
-    }
-};
-
 static jboolean nativeSetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobject tokenObj,
                                                  jobject DesiredDisplayModeSpecs) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == nullptr) return JNI_FALSE;
 
-    ui::DisplayModeId defaultMode = env->GetIntField(DesiredDisplayModeSpecs,
-                                                     gDesiredDisplayModeSpecsClassInfo.defaultMode);
-    jboolean allowGroupSwitching =
+    const auto makeRanges = [env](jobject obj) {
+        const auto makeRange = [env](jobject obj) {
+            gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range;
+            range.min = env->GetFloatField(obj, gRefreshRateRangeClassInfo.min);
+            range.max = env->GetFloatField(obj, gRefreshRateRangeClassInfo.max);
+            return range;
+        };
+
+        gui::DisplayModeSpecs::RefreshRateRanges ranges;
+        ranges.physical = makeRange(env->GetObjectField(obj, gRefreshRateRangesClassInfo.physical));
+        ranges.render = makeRange(env->GetObjectField(obj, gRefreshRateRangesClassInfo.render));
+        return ranges;
+    };
+
+    gui::DisplayModeSpecs specs;
+    specs.defaultMode = env->GetIntField(DesiredDisplayModeSpecs,
+                                         gDesiredDisplayModeSpecsClassInfo.defaultMode);
+    specs.allowGroupSwitching =
             env->GetBooleanField(DesiredDisplayModeSpecs,
                                  gDesiredDisplayModeSpecsClassInfo.allowGroupSwitching);
 
-    const jobject primaryRangesObject =
-            env->GetObjectField(DesiredDisplayModeSpecs,
-                                gDesiredDisplayModeSpecsClassInfo.primaryRanges);
-    const jobject appRequestRangesObject =
-            env->GetObjectField(DesiredDisplayModeSpecs,
-                                gDesiredDisplayModeSpecsClassInfo.appRequestRanges);
-    const RefreshRateRanges primaryRanges(env, primaryRangesObject);
-    const RefreshRateRanges appRequestRanges(env, appRequestRangesObject);
+    specs.primaryRanges =
+            makeRanges(env->GetObjectField(DesiredDisplayModeSpecs,
+                                           gDesiredDisplayModeSpecsClassInfo.primaryRanges));
+    specs.appRequestRanges =
+            makeRanges(env->GetObjectField(DesiredDisplayModeSpecs,
+                                           gDesiredDisplayModeSpecsClassInfo.appRequestRanges));
 
-    size_t result =
-            SurfaceComposerClient::setDesiredDisplayModeSpecs(token, defaultMode,
-                                                              allowGroupSwitching,
-                                                              primaryRanges.physical.min,
-                                                              primaryRanges.physical.max,
-                                                              appRequestRanges.physical.min,
-                                                              appRequestRanges.physical.max);
+    size_t result = SurfaceComposerClient::setDesiredDisplayModeSpecs(token, specs);
     return result == NO_ERROR ? JNI_TRUE : JNI_FALSE;
 }
 
@@ -1269,33 +1243,26 @@
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == nullptr) return nullptr;
 
-    ui::DisplayModeId defaultMode;
-    bool allowGroupSwitching;
-    float primaryPhysicalRefreshRateMin;
-    float primaryPhysicalRefreshRateMax;
-    float appRequestPhysicalRefreshRateMin;
-    float appRequestPhysicalRefreshRateMax;
-    if (SurfaceComposerClient::getDesiredDisplayModeSpecs(token, &defaultMode, &allowGroupSwitching,
-                                                          &primaryPhysicalRefreshRateMin,
-                                                          &primaryPhysicalRefreshRateMax,
-                                                          &appRequestPhysicalRefreshRateMin,
-                                                          &appRequestPhysicalRefreshRateMax) !=
-        NO_ERROR) {
+    const auto rangesToJava = [env](const gui::DisplayModeSpecs::RefreshRateRanges& ranges) {
+        const auto rangeToJava =
+                [env](const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& range) {
+                    return env->NewObject(gRefreshRateRangeClassInfo.clazz,
+                                          gRefreshRateRangeClassInfo.ctor, range.min, range.max);
+                };
+
+        return env->NewObject(gRefreshRateRangesClassInfo.clazz, gRefreshRateRangesClassInfo.ctor,
+                              rangeToJava(ranges.physical), rangeToJava(ranges.render));
+    };
+
+    gui::DisplayModeSpecs specs;
+    if (SurfaceComposerClient::getDesiredDisplayModeSpecs(token, &specs) != NO_ERROR) {
         return nullptr;
     }
 
-    const RefreshRateRange primaryPhysicalRange(primaryPhysicalRefreshRateMin,
-                                                primaryPhysicalRefreshRateMax);
-    const RefreshRateRange appRequestPhysicalRange(appRequestPhysicalRefreshRateMin,
-                                                   appRequestPhysicalRefreshRateMax);
-
-    // TODO(b/241460058): populate the render ranges
-    const RefreshRateRanges primaryRanges(primaryPhysicalRange, primaryPhysicalRange);
-    const RefreshRateRanges appRequestRanges(appRequestPhysicalRange, appRequestPhysicalRange);
-
     return env->NewObject(gDesiredDisplayModeSpecsClassInfo.clazz,
-                          gDesiredDisplayModeSpecsClassInfo.ctor, defaultMode, allowGroupSwitching,
-                          primaryRanges.toJava(env), appRequestRanges.toJava(env));
+                          gDesiredDisplayModeSpecsClassInfo.ctor, specs.defaultMode,
+                          specs.allowGroupSwitching, rangesToJava(specs.primaryRanges),
+                          rangesToJava(specs.appRequestRanges));
 }
 
 static jobject nativeGetDisplayNativePrimaries(JNIEnv* env, jclass, jobject tokenObj) {
diff --git a/core/jni/com_android_internal_expresslog_Counter.cpp b/core/jni/com_android_internal_expresslog_Counter.cpp
new file mode 100644
index 0000000..d4a8c23
--- /dev/null
+++ b/core/jni/com_android_internal_expresslog_Counter.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/hash/farmhash.h>
+
+#include "core_jni_helpers.h"
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+static jclass g_stringClass = nullptr;
+
+/**
+ * Class:     com_android_internal_expresslog_Counter
+ * Method:    hashString
+ * Signature: (Ljava/lang/String;)J
+ */
+static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
+    ScopedUtfChars name(env, metricNameObj);
+    if (name.c_str() == nullptr) {
+        return 0;
+    }
+
+    return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
+}
+
+static const JNINativeMethod g_methods[] = {
+        {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
+};
+
+static const char* const kCounterPathName = "com/android/internal/expresslog/Counter";
+
+namespace android {
+
+int register_com_android_internal_expresslog_Counter(JNIEnv* env) {
+    jclass stringClass = FindClassOrDie(env, "java/lang/String");
+    g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+    return RegisterMethodsOrDie(env, kCounterPathName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 16e0a59..87f47a4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6908,13 +6908,6 @@
                   android:exported="false">
         </activity>
 
-        <activity android:name="com.android.internal.app.LogAccessDialogActivity"
-                  android:theme="@style/Theme.Translucent.NoTitleBar"
-                  android:process=":ui"
-                  android:excludeFromRecents="true"
-                  android:exported="false">
-        </activity>
-
         <activity android:name="com.android.server.notification.NASLearnMoreActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true"
@@ -7211,6 +7204,10 @@
             </intent-filter>
         </service>
 
+        <service android:name="com.android.server.art.BackgroundDexOptJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <provider
             android:name="com.android.server.textclassifier.IconsContentProvider"
             android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 88bf18c..eaadc20 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -54,6 +54,12 @@
         com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
     </string>
 
+    <!-- Component name of the activity used to inform a user about a sensor privacy update from
+     SW/HW privacy switches. -->
+    <string name="config_sensorStateChangedActivity" translatable="false">
+        com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
+    </string>
+
     <!-- Component name of the activity that shows the request for access to a usb device. -->
     <string name="config_usbPermissionActivity" translatable="false">
         com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity
diff --git a/core/res/res/values-television/styles.xml b/core/res/res/values-television/styles.xml
deleted file mode 100644
index ad9140c..0000000
--- a/core/res/res/values-television/styles.xml
+++ /dev/null
@@ -1,21 +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.
-  -->
-<resources>
-    <style name="PermissionGrantButtonTop"
-        parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" />
-    <style name="PermissionGrantButtonBottom"
-        parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" />
-</resources>
\ No newline at end of file
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index dff7751..9e735d0 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -166,12 +166,12 @@
     </string-array>
 
     <array name="sim_colors">
-        <item>@color/Teal_700</item>
-        <item>@color/Blue_700</item>
-        <item>@color/Indigo_700</item>
-        <item>@color/Purple_700</item>
-        <item>@color/Pink_700</item>
-        <item>@color/Red_700</item>
+        <item>@color/SIM_color_cyan</item>
+        <item>@color/SIM_color_blue</item>
+        <item>@color/SIM_color_green</item>
+        <item>@color/SIM_color_purple</item>
+        <item>@color/SIM_color_pink</item>
+        <item>@color/SIM_color_orange</item>
     </array>
 
     <!-- Used in ResolverTargetActionsDialogFragment -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 77d7c43..8c356b4 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -198,18 +198,18 @@
     <color name="instant_app_badge">#ff757575</color><!-- Grey -->
 
     <!-- Multi-sim sim colors -->
-    <color name="Teal_700">#ff00796b</color>
-    <color name="Teal_800">#ff00695c</color>
-    <color name="Blue_700">#ff3367d6</color>
-    <color name="Blue_800">#ff2a56c6</color>
-    <color name="Indigo_700">#ff303f9f</color>
-    <color name="Indigo_800">#ff283593</color>
-    <color name="Purple_700">#ff7b1fa2</color>
-    <color name="Purple_800">#ff6a1b9a</color>
-    <color name="Pink_700">#ffc2185b</color>
-    <color name="Pink_800">#ffad1457</color>
-    <color name="Red_700">#ffc53929</color>
-    <color name="Red_800">#ffb93221</color>
+    <color name="SIM_color_cyan">#ff006D74</color><!-- Material Custom Cyan -->
+    <color name="SIM_dark_mode_color_cyan">#ff4DD0E1</color><!-- Material Cyan 300 -->
+    <color name="SIM_color_blue">#ff185ABC</color><!-- Material Blue 800 -->
+    <color name="SIM_dark_mode_color_blue">#ff8AB4F8</color><!-- Material Blue 300 -->
+    <color name="SIM_color_green">#ff137333</color><!-- Material Green 800 -->
+    <color name="SIM_dark_mode_color_green">#ff81C995</color><!-- Material Green 300 -->
+    <color name="SIM_color_purple">#ff7627bb</color><!-- Material Purple 800 -->
+    <color name="SIM_dark_mode_color_purple">#ffC58AF9</color><!-- Material Purple 300 -->
+    <color name="SIM_color_pink">#ffb80672</color><!-- Material Pink 800 -->
+    <color name="SIM_dark_mode_color_pink">#ffff8bcb</color><!-- Material Pink 300 -->
+    <color name="SIM_color_orange">#ff995400</color><!-- Material Custom Orange -->
+    <color name="SIM_dark_mode_color_orange">#fffcad70</color><!-- Material Orange 300 -->
 
     <color name="resize_shadow_start_color">#2a000000</color>
     <color name="resize_shadow_end_color">#00000000</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c122e68..fcf38c5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -342,22 +342,6 @@
     <!-- Mask to use when checking skb mark defined in config_networkWakeupPacketMark above. -->
     <integer name="config_networkWakeupPacketMask">0</integer>
 
-    <!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
-         Those frames are identified by the field Eth-type having values
-         less than 0x600 -->
-    <bool translatable="false" name="config_apfDrop802_3Frames">true</bool>
-
-    <!-- An array of Denylisted EtherType, packets with EtherTypes within this array
-         will be dropped
-         TODO: need to put proper values, these are for testing purposes only -->
-    <integer-array translatable="false" name="config_apfEthTypeBlackList">
-        <item>0x88A2</item>
-        <item>0x88A4</item>
-        <item>0x88B8</item>
-        <item>0x88CD</item>
-        <item>0x88E3</item>
-    </integer-array>
-
     <!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual
          device behaviour is controlled by Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE.
          This is the default value of that setting. -->
@@ -2027,10 +2011,6 @@
          STREAM_MUSIC as if it's on TV platform. -->
     <bool name="config_single_volume">false</bool>
 
-    <!-- Flag indicating whether notification and ringtone volumes
-         are controlled together (aliasing is true) or not. -->
-    <bool name="config_alias_ring_notif_stream_types">true</bool>
-
     <!-- The number of volume steps for the notification stream -->
     <integer name="config_audio_notif_vol_steps">7</integer>
 
@@ -2456,8 +2436,10 @@
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
     <bool name="config_dismissDreamOnActivityStart">false</bool>
 
-    <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
-    <string name="config_loggable_dream_prefix" translatable="false"></string>
+    <!-- The prefixes of dream component names that are loggable.
+         Matched against ComponentName#flattenToString() for dream components.
+         If empty, logs "other" for all. -->
+    <string-array name="config_loggable_dream_prefixes"></string-array>
 
     <!-- ComponentName of a dream to show whenever the system would otherwise have
          gone to sleep.  When the PowerManager is asked to go to sleep, it will instead
@@ -2903,7 +2885,7 @@
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingActivity</string>
 
     <!-- Name of the activity that prompts the secondary user to acknowledge they need to
-         switch to the primary user to enable USB debugging.
+         switch to an admin user to enable USB debugging.
          Can be customized for other product types -->
     <string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
@@ -2915,7 +2897,7 @@
             >com.android.systemui/com.android.systemui.wifi.WifiDebuggingActivity</string>
 
     <!-- Name of the activity that prompts the secondary user to acknowledge they need to
-         switch to the primary user to enable wireless debugging.
+         switch to an admin user to enable wireless debugging.
          Can be customized for other product types -->
     <string name="config_customAdbWifiNetworkConfirmationSecondaryUserComponent"
             >com.android.systemui/com.android.systemui.wifi.WifiDebuggingSecondaryUserActivity</string>
@@ -2942,6 +2924,9 @@
     <string name="config_usbResolverActivity" translatable="false"
             >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
 
+    <!-- Component name of the activity used to inform a user about a sensor privacy state chage. -->
+    <string name="config_sensorStateChangedActivity" translatable="false"></string>
+
     <!-- Component name of the activity used to inform a user about a sensory being blocked because
      of privacy settings. -->
     <string name="config_sensorUseStartedActivity" translatable="false"
@@ -5950,4 +5935,9 @@
     <!-- Whether the lock screen is allowed to run its own live wallpaper,
          different from the home screen wallpaper. -->
     <bool name="config_independentLockscreenLiveWallpaper">false</bool>
+
+    <!-- List of certificate to be used for font fs-verity integrity verification -->
+    <string-array translatable="false" name="config_fontManagerServiceCerts">
+    </string-array>
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1f459c6..3260420d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5759,32 +5759,6 @@
     <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
     <string name="harmful_app_warning_title">Harmful app detected</string>
 
-    <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] -->
-    <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string>
-    <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=40] -->
-    <string name="log_access_confirmation_allow">Allow one-time access</string>
-    <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
-    <string name="log_access_confirmation_deny">Don\u2019t allow</string>
-
-    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
-    <string name="log_access_confirmation_body" product="default">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
-        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
-    </string>
-
-    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
-    <string name="log_access_confirmation_body" product="tv">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
-        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
-    </string>
-
-    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
-    <string name="log_access_confirmation_learn_more" product="default" translatable="false">&lt;a href="https://support.google.com/android?p=system_logs#topic=7313011"&gt;Learn more&lt;/a&gt;</string>
-
-    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
-    <string name="log_access_confirmation_learn_more" product="tv" translatable="false"></string>
-
-    <!-- Privacy notice do not show [CHAR LIMIT=20] -->
-    <string name="log_access_do_not_show_again">Don\u2019t show again</string>
-
     <!-- Text describing a permission request for one app to show another app's
          slices [CHAR LIMIT=NONE] -->
     <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 2dd563d..476c18e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1541,40 +1541,4 @@
     <style name="NotificationTombstoneAction" parent="NotificationAction">
       <item name="textColor">#555555</item>
     </style>
-
-    <!-- The style for log access consent text -->
-    <style name="AllowLogAccess">
-        <item name="android:textSize">24sp</item>
-        <item name="android:fontFamily">google-sans</item>
-    </style>
-
-    <style name="PrimaryAllowLogAccess">
-        <item name="android:textSize">14sp</item>
-        <item name="android:fontFamily">google-sans-text</item>
-    </style>
-
-    <style name="PermissionGrantButtonTextAppearance">
-        <item name="android:fontFamily">google-sans-medium</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:textColor">@android:color/system_neutral1_900</item>
-    </style>
-
-    <style name="PermissionGrantButtonTop"
-           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
-        <item name="android:layout_width">332dp</item>
-        <item name="android:layout_height">56dp</item>
-        <item name="android:layout_marginTop">2dp</item>
-        <item name="android:layout_marginBottom">2dp</item>
-        <item name="android:background">@drawable/grant_permissions_buttons_top</item>
-    </style>
-
-    <style name="PermissionGrantButtonBottom"
-           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
-        <item name="android:layout_width">332dp</item>
-        <item name="android:layout_height">56dp</item>
-        <item name="android:layout_marginTop">2dp</item>
-        <item name="android:layout_marginBottom">2dp</item>
-        <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
-    </style>
-
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9e3235b8..7b62841 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -282,7 +282,6 @@
   <java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
   <java-symbol type="bool" name="action_bar_embed_tabs" />
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
-  <java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
   <java-symbol type="integer" name="config_audio_notif_vol_default" />
   <java-symbol type="integer" name="config_audio_notif_vol_steps" />
   <java-symbol type="integer" name="config_audio_ring_vol_default" />
@@ -374,6 +373,7 @@
   <java-symbol type="string" name="config_usbResolverActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity_hwToggle" />
+  <java-symbol type="string" name="config_sensorStateChangedActivity" />
   <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
@@ -2045,8 +2045,6 @@
   <java-symbol type="integer" name="config_networkAvoidBadWifi" />
   <java-symbol type="integer" name="config_networkWakeupPacketMark" />
   <java-symbol type="integer" name="config_networkWakeupPacketMask" />
-  <java-symbol type="bool" name="config_apfDrop802_3Frames" />
-  <java-symbol type="array" name="config_apfEthTypeBlackList" />
   <java-symbol type="integer" name="config_networkDefaultDailyMultipathQuotaBytes" />
   <java-symbol type="integer" name="config_networkMeteredMultipathPreference" />
   <java-symbol type="array" name="config_networkSupportedKeepaliveCount" />
@@ -2246,7 +2244,7 @@
   <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
   <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
   <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
-  <java-symbol type="string" name="config_loggable_dream_prefix" />
+  <java-symbol type="array" name="config_loggable_dream_prefixes" />
   <java-symbol type="string" name="config_dozeComponent" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_title" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2309,6 +2307,7 @@
   <java-symbol type="id" name="media_actions" />
 
   <java-symbol type="dimen" name="config_mediaMetadataBitmapMaxSize" />
+  <java-symbol type="array" name="config_fontManagerServiceCerts" />
 
     <!-- From SystemUI -->
   <java-symbol type="anim" name="push_down_in" />
@@ -3939,17 +3938,6 @@
   <java-symbol type="string" name="harmful_app_warning_title" />
   <java-symbol type="layout" name="harmful_app_warning_dialog" />
 
-  <java-symbol type="string" name="log_access_confirmation_allow" />
-  <java-symbol type="string" name="log_access_confirmation_deny" />
-  <java-symbol type="string" name="log_access_confirmation_title" />
-  <java-symbol type="string" name="log_access_confirmation_body" />
-  <java-symbol type="string" name="log_access_confirmation_learn_more" />
-  <java-symbol type="layout" name="log_access_user_consent_dialog_permission" />
-  <java-symbol type="id" name="log_access_dialog_title" />
-  <java-symbol type="id" name="log_access_dialog_body" />
-  <java-symbol type="id" name="log_access_dialog_allow_button" />
-  <java-symbol type="id" name="log_access_dialog_deny_button" />
-
   <java-symbol type="string" name="config_defaultAssistantAccessComponent" />
 
   <java-symbol type="string" name="slices_permission_request" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
new file mode 100644
index 0000000..2fa3f876
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public final class DefaultRadioTunerTest {
+
+    private static final RadioTuner DEFAULT_RADIO_TUNER = new RadioTuner() {
+        @Override
+        public void close() {}
+
+        @Override
+        public int setConfiguration(RadioManager.BandConfig config) {
+            return 0;
+        }
+
+        @Override
+        public int getConfiguration(RadioManager.BandConfig[] config) {
+            return 0;
+        }
+
+        @Override
+        public int setMute(boolean mute) {
+            return 0;
+        }
+
+        @Override
+        public boolean getMute() {
+            return false;
+        }
+
+        @Override
+        public int step(int direction, boolean skipSubChannel) {
+            return 0;
+        }
+
+        @Override
+        public int scan(int direction, boolean skipSubChannel) {
+            return 0;
+        }
+
+        @Override
+        public int tune(int channel, int subChannel) {
+            return 0;
+        }
+
+        @Override
+        public void tune(@NonNull ProgramSelector selector) {}
+
+        @Override
+        public int cancel() {
+            return 0;
+        }
+
+        @Override
+        public void cancelAnnouncement() {}
+
+        @Override
+        public int getProgramInformation(RadioManager.ProgramInfo[] info) {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public Bitmap getMetadataImage(int id) {
+            return null;
+        }
+
+        @Override
+        public boolean startBackgroundScan() {
+            return false;
+        }
+
+        @NonNull
+        @Override
+        public List<RadioManager.ProgramInfo> getProgramList(
+                @Nullable Map<String, String> vendorFilter) {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public boolean isAnalogForced() {
+            return false;
+        }
+
+        @Override
+        public void setAnalogForced(boolean isForced) {}
+
+        @Override
+        public boolean isAntennaConnected() {
+            return false;
+        }
+
+        @Override
+        public boolean hasControl() {
+            return false;
+        }
+    };
+
+    @Test
+    public void getDynamicProgramList_forRadioTuner_returnsNull() {
+        assertWithMessage("Dynamic program list obtained from default radio tuner")
+                .that(DEFAULT_RADIO_TUNER.getDynamicProgramList(new ProgramList.Filter())).isNull();
+    }
+
+    @Test
+    public void isConfigFlagSupported_forRadioTuner_throwsException() {
+        assertWithMessage("Dynamic program list obtained from default radio tuner")
+                .that(DEFAULT_RADIO_TUNER.isConfigFlagSupported(/* flag= */ 1)).isFalse();
+    }
+
+    @Test
+    public void isConfigFlagSet_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
+        });
+    }
+
+    @Test
+    public void setConfigFlag_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false);
+        });
+    }
+
+    @Test
+    public void setParameters_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue"));
+        });
+    }
+
+    @Test
+    public void getParameters_forRadioTuner_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            DEFAULT_RADIO_TUNER.getParameters(List.of("testKey"));
+        });
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
new file mode 100644
index 0000000..2b9de18
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class ProgramListTest {
+
+    private static final int CREATOR_ARRAY_SIZE = 3;
+    private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
+
+    private static final boolean IS_PURGE = false;
+    private static final boolean IS_COMPLETE = true;
+
+    private static final boolean INCLUDE_CATEGORIES = true;
+    private static final boolean EXCLUDE_MODIFICATIONS = false;
+
+    private static final ProgramSelector.Identifier FM_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+                    /* value= */ 94300);
+    private static final ProgramSelector.Identifier RDS_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
+    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+                    /* value= */ 0x10000111);
+    private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+                    /* value= */ 0x1013);
+    private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo(
+            createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER));
+    private static final RadioManager.ProgramInfo RDS_PROGRAM_INFO = createFmProgramInfo(
+            createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, RDS_IDENTIFIER));
+
+    private static final Set<Integer> FILTER_IDENTIFIER_TYPES = Set.of(
+            ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
+    private static final Set<ProgramSelector.Identifier> FILTER_IDENTIFIERS = Set.of(FM_IDENTIFIER);
+
+    private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
+            IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+            Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+    private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
+            /* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
+    private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
+            FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+    private static final Map<String, String> VENDOR_FILTER = Map.of("testVendorKey1",
+            "testVendorValue1", "testVendorKey2", "testVendorValue2");
+
+    private final Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    };
+
+    private RadioTuner mRadioTuner;
+    private ITunerCallback mTunerCallback;
+    private ProgramList mProgramList;
+
+    private ProgramList.ListCallback[] mListCallbackMocks;
+    private ProgramList.OnCompleteListener[] mOnCompleteListenerMocks;
+    @Mock
+    private IRadioService mRadioServiceMock;
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private ITuner mTunerMock;
+    @Mock
+    private RadioTuner.Callback mTunerCallbackMock;
+
+    @Test
+    public void getIdentifierTypes_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes())
+                .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES);
+    }
+
+    @Test
+    public void getIdentifiers_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filtered identifiers").that(filter.getIdentifiers())
+                .containsExactlyElementsIn(FILTER_IDENTIFIERS);
+    }
+
+    @Test
+    public void areCategoriesIncluded_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filter including categories")
+                .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES);
+    }
+
+    @Test
+    public void areModificationsExcluded_forFilter() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filter excluding modifications")
+                .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS);
+    }
+
+    @Test
+    public void getVendorFilter_forFilterWithoutVendorFilter_returnsNull() {
+        ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Filter vendor obtained from filter without vendor filter")
+                .that(filter.getVendorFilter()).isNull();
+    }
+
+    @Test
+    public void getVendorFilter_forFilterWithVendorFilter() {
+        ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER);
+
+        assertWithMessage("Filter vendor obtained from filter with vendor filter")
+                .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER);
+    }
+
+    @Test
+    public void describeContents_forFilter() {
+        assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void hashCode_withTheSameFilters_equals() {
+        ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+                FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+        assertWithMessage("Hash code of the same filter")
+                .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode());
+    }
+
+    @Test
+    public void hashCode_withDifferentFilters_notEquals() {
+        ProgramList.Filter filterCompared = new ProgramList.Filter();
+
+        assertWithMessage("Hash code of the different filter")
+                .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode());
+    }
+
+    @Test
+    public void writeToParcel_forFilter() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_FILTER.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ProgramList.Filter filterFromParcel =
+                ProgramList.Filter.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Filter created from parcel")
+                .that(filterFromParcel).isEqualTo(TEST_FILTER);
+    }
+
+    @Test
+    public void newArray_forFilterCreator() {
+        ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void isPurge_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
+    }
+
+    @Test
+    public void isComplete_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
+    }
+
+    @Test
+    public void getModified_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Modified program info in chunk")
+                .that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+    }
+
+    @Test
+    public void getRemoved_forChunk() {
+        ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+                Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+        assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
+                .containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+    }
+
+    @Test
+    public void describeContents_forChunk() {
+        assertWithMessage("Chunk contents").that(FM_RDS_ADD_CHUNK.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forChunk() {
+        Parcel parcel = Parcel.obtain();
+
+        FM_RDS_ADD_CHUNK.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ProgramList.Chunk chunkFromParcel =
+                ProgramList.Chunk.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Chunk created from parcel")
+                .that(chunkFromParcel).isEqualTo(FM_RDS_ADD_CHUNK);
+    }
+
+    @Test
+    public void newArray_forChunkCreator() {
+        ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void getDynamicProgramList_forTunerAdapter() throws Exception {
+        createRadioTuner();
+
+        mRadioTuner.getDynamicProgramList(TEST_FILTER);
+
+        verify(mTunerMock).startProgramListUpdates(TEST_FILTER);
+    }
+
+    @Test
+    public void getDynamicProgramList_forTunerAdapterWithServiceDied_throwsException()
+            throws Exception {
+        createRadioTuner();
+        doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        });
+
+        assertWithMessage("Exception for radio HAL client service died")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
+    public void onProgramListUpdated_withNewIdsAdded_invokesMockedCallbacks() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        addOnCompleteListeners(/* numListeners= */ 1);
+
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(RDS_IDENTIFIER);
+        verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
+        assertWithMessage("Program info in program list after adding FM and RDS info")
+                .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+    }
+
+    @Test
+    public void onProgramListUpdated_withIdsRemoved_invokesMockedCallbacks() throws Exception {
+        ProgramList.Chunk fmRemovedChunk = new ProgramList.Chunk(/* purge= */ false,
+                /* complete= */ false, new ArraySet<>(), Set.of(FM_IDENTIFIER));
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        mTunerCallback.onProgramListUpdated(fmRemovedChunk);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+        assertWithMessage("Program info in program list after removing FM id")
+                .that(mProgramList.toList()).containsExactly(RDS_PROGRAM_INFO);
+        assertWithMessage("Program info FM identifier")
+                .that(mProgramList.get(RDS_IDENTIFIER)).isEqualTo(RDS_PROGRAM_INFO);
+    }
+
+    @Test
+    public void onProgramListUpdated_withIncompleteChunk_notInvokesOnCompleteListener()
+            throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        addOnCompleteListeners(/* numListeners= */ 1);
+
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
+    }
+
+    @Test
+    public void onProgramListUpdated_withPurgeChunk() throws Exception {
+        ProgramList.Chunk purgeChunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, new ArraySet<>(), new ArraySet<>());
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        mTunerCallback.onProgramListUpdated(purgeChunk);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(RDS_IDENTIFIER);
+        assertWithMessage("Program list after purge chunk applied")
+                .that(mProgramList.toList()).isEmpty();
+    }
+
+    @Test
+    public void onItemChanged_forListCallbackRegisteredWithExecutor_invokesWhenIdAdded()
+            throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        ProgramList.ListCallback listCallbackMock = mock(ProgramList.ListCallback.class);
+        mProgramList.registerListCallback(mExecutor, listCallbackMock);
+
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        verify(listCallbackMock, CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+    }
+
+    @Test
+    public void onItemRemoved_forListCallbackRegisteredWithExecutor_invokesWhenIdRemoved()
+            throws Exception {
+        ProgramList.Chunk purgeChunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, new ArraySet<>(), new ArraySet<>());
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        ProgramList.ListCallback listCallbackMock = mock(ProgramList.ListCallback.class);
+        mProgramList.registerListCallback(mExecutor, listCallbackMock);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        mTunerCallback.onProgramListUpdated(purgeChunk);
+
+        verify(listCallbackMock, CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+    }
+
+    @Test
+    public void onProgramListUpdated_withMultipleListCallBacks() throws Exception {
+        int numCallbacks = 3;
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(numCallbacks);
+
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        for (int index = 0; index < numCallbacks; index++) {
+            verify(mListCallbackMocks[index], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+        }
+    }
+
+    @Test
+    public void unregisterListCallback_withProgramUpdated_notInvokesCallback() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+
+        mProgramList.unregisterListCallback(mListCallbackMocks[0]);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onItemChanged(any());
+    }
+
+    @Test
+    public void addOnCompleteListener_withExecutor() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        ProgramList.OnCompleteListener onCompleteListenerMock =
+                mock(ProgramList.OnCompleteListener.class);
+
+        mProgramList.addOnCompleteListener(mExecutor, onCompleteListenerMock);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        verify(onCompleteListenerMock, CALLBACK_TIMEOUT).onComplete();
+    }
+
+    @Test
+    public void onProgramListUpdated_withMultipleOnCompleteListeners() throws Exception {
+        int numListeners = 3;
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        addOnCompleteListeners(numListeners);
+
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        for (int index = 0; index < numListeners; index++) {
+            verify(mOnCompleteListenerMocks[index], CALLBACK_TIMEOUT).onComplete();
+        }
+    }
+
+    @Test
+    public void removeOnCompleteListener_withProgramUpdated_notInvokesListener() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        addOnCompleteListeners(/* numListeners= */ 1);
+
+        mProgramList.removeOnCompleteListener(mOnCompleteListenerMocks[0]);
+        mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+        verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
+    }
+
+    @Test
+    public void close_forProgramList_invokesStopProgramListUpdates() throws Exception {
+        createRadioTuner();
+        ProgramList programList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+
+        programList.close();
+
+        verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates();
+    }
+
+    private static ProgramSelector createProgramSelector(int programType,
+            ProgramSelector.Identifier identifier) {
+        return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+                /* vendorIds= */ null);
+    }
+
+    private static RadioManager.ProgramInfo createFmProgramInfo(ProgramSelector selector) {
+        return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
+                selector.getPrimaryId(), /* relatedContents= */ null, /* infoFlags= */ 0,
+                /* signalQuality= */ 1, new RadioMetadata.Builder().build(),
+                /* vendorInfo= */ null);
+    }
+
+    private void createRadioTuner() throws Exception {
+        RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+        RadioManager.BandConfig band = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
+                        /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200,
+                        /* stereo= */ true, /* rds= */ false, /* ta= */ false, /* af= */ false,
+                        /* es= */ false));
+
+        doAnswer(invocation -> {
+            mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
+            return mTunerMock;
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+
+        mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
+                /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
+    }
+
+    private void registerListCallbacks(int numCallbacks) {
+        mListCallbackMocks = new ProgramList.ListCallback[numCallbacks];
+        for (int index = 0; index < numCallbacks; index++) {
+            mListCallbackMocks[index] = mock(ProgramList.ListCallback.class);
+            mProgramList.registerListCallback(mListCallbackMocks[index]);
+        }
+    }
+
+    private void addOnCompleteListeners(int numListeners) {
+        mOnCompleteListenerMocks = new ProgramList.OnCompleteListener[numListeners];
+        for (int index = 0; index < numListeners; index++) {
+            mOnCompleteListenerMocks[index] = mock(ProgramList.OnCompleteListener.class);
+            mProgramList.addOnCompleteListener(mOnCompleteListenerMocks[index]);
+        }
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index f838a5d..365b901 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.radio.Announcement;
 import android.hardware.radio.IAnnouncementListener;
@@ -46,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -89,7 +91,8 @@
             createAmBandDescriptor();
     private static final RadioManager.FmBandConfig FM_BAND_CONFIG = createFmBandConfig();
     private static final RadioManager.AmBandConfig AM_BAND_CONFIG = createAmBandConfig();
-    private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties();
+    private static final RadioManager.ModuleProperties AMFM_PROPERTIES =
+            createAmFmProperties(/* dabFrequencyTable= */ null);
 
     /**
      * Info flags with live, tuned and stereo enabled
@@ -709,12 +712,20 @@
     }
 
     @Test
-    public void getDabFrequencyTable_forModuleProperties() {
+    public void getDabFrequencyTable_forModulePropertiesInitializedWithNullTable() {
         assertWithMessage("Properties DAB frequency table")
                 .that(AMFM_PROPERTIES.getDabFrequencyTable()).isNull();
     }
 
     @Test
+    public void getDabFrequencyTable_forModulePropertiesInitializedWithEmptyTable() {
+        RadioManager.ModuleProperties properties = createAmFmProperties(new ArrayMap<>());
+
+        assertWithMessage("Properties DAB frequency table")
+                .that(properties.getDabFrequencyTable()).isNull();
+    }
+
+    @Test
     public void getVendorInfo_forModuleProperties() {
         assertWithMessage("Properties vendor info")
                 .that(AMFM_PROPERTIES.getVendorInfo()).isEmpty();
@@ -734,8 +745,37 @@
     }
 
     @Test
+    public void writeToParcel_forModulePropertiesWithNullDabFrequencyTable() {
+        Parcel parcel = Parcel.obtain();
+
+        AMFM_PROPERTIES.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.ModuleProperties modulePropertiesFromParcel =
+                RadioManager.ModuleProperties.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Module properties created from parcel")
+                .that(modulePropertiesFromParcel).isEqualTo(AMFM_PROPERTIES);
+    }
+
+    @Test
+    public void writeToParcel_forModulePropertiesWithNonnullDabFrequencyTable() {
+        Parcel parcel = Parcel.obtain();
+        RadioManager.ModuleProperties propertiesToParcel = createAmFmProperties(
+                Map.of("5A", 174928, "12D", 229072));
+
+        propertiesToParcel.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.ModuleProperties modulePropertiesFromParcel =
+                RadioManager.ModuleProperties.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Module properties created from parcel")
+                .that(modulePropertiesFromParcel).isEqualTo(propertiesToParcel);
+    }
+
+    @Test
     public void equals_withSameProperties_returnsTrue() {
-        RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+        RadioManager.ModuleProperties propertiesCompared =
+                createAmFmProperties(/* dabFrequencyTable= */ null);
 
         assertWithMessage("The same module properties")
                 .that(AMFM_PROPERTIES).isEqualTo(propertiesCompared);
@@ -747,7 +787,7 @@
                 PROPERTIES_ID + 1, SERVICE_NAME, CLASS_ID, IMPLEMENTOR, PRODUCT, VERSION,
                 SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, IS_INITIALIZATION_REQUIRED,
                 IS_CAPTURE_SUPPORTED, /* bands= */ null, IS_BG_SCAN_SUPPORTED,
-                SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, /* dabFrequencyTable= */ null,
+                SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, Map.of("5A", 174928),
                 /* vendorInfo= */ null);
 
         assertWithMessage("Module properties of different id")
@@ -756,7 +796,8 @@
 
     @Test
     public void hashCode_withSameModuleProperties_equals() {
-        RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+        RadioManager.ModuleProperties propertiesCompared =
+                createAmFmProperties(/* dabFrequencyTable= */ null);
 
         assertWithMessage("Hash code of the same module properties")
                 .that(propertiesCompared.hashCode()).isEqualTo(AMFM_PROPERTIES.hashCode());
@@ -989,13 +1030,14 @@
         verify(mCloseHandleMock).close();
     }
 
-    private static RadioManager.ModuleProperties createAmFmProperties() {
+    private static RadioManager.ModuleProperties createAmFmProperties(
+            @Nullable Map<String, Integer>  dabFrequencyTable) {
         return new RadioManager.ModuleProperties(PROPERTIES_ID, SERVICE_NAME, CLASS_ID,
                 IMPLEMENTOR, PRODUCT, VERSION, SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES,
                 IS_INITIALIZATION_REQUIRED, IS_CAPTURE_SUPPORTED,
                 new RadioManager.BandDescriptor[]{AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR},
                 IS_BG_SCAN_SUPPORTED, SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES,
-                /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+                dabFrequencyTable, /* vendorInfo= */ null);
     }
 
     private static RadioManager.FmBandDescriptor createFmBandDescriptor() {
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index fe3ab62..bdba6a1 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -280,6 +280,16 @@
     }
 
     @Test
+    public void startBackgroundScan_forTunerAdapter() throws Exception {
+        when(mTunerMock.startBackgroundScan()).thenReturn(false);
+
+        boolean scanStatus = mRadioTuner.startBackgroundScan();
+
+        verify(mTunerMock).startBackgroundScan();
+        assertWithMessage("Status for starting background scan").that(scanStatus).isFalse();
+    }
+
+    @Test
     public void isAnalogForced_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
 
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
index 100eb99..b3af895 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
@@ -145,7 +145,8 @@
         mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
         mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         mIntentFilter.addAction(TetheringManager.ACTION_TETHER_STATE_CHANGED);
-        mContext.registerReceiver(mWifiReceiver, mIntentFilter);
+        mContext.registerReceiver(mWifiReceiver, mIntentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         logv("Clear Wifi before we start the test.");
         removeConfiguredNetworksAndDisableWifi();
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
index 1a63660..191756a 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
@@ -99,7 +99,8 @@
         // Register a download receiver for ACTION_DOWNLOAD_COMPLETE
         mDownloadReceiver = new DownloadReceiver();
         mContext.registerReceiver(mDownloadReceiver,
-                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         // Register a wifi receiver
         mWifiReceiver = new WifiReceiver();
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 04952bd..e2cdbf3 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -25,6 +25,11 @@
         <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+        <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+    </target_preparer>
+
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
 
     <option name="test-tag" value="FrameworksCoreTests" />
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index f4709ff..cb66fc8 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -471,7 +471,7 @@
     protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
         MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
         mContext.registerReceiver(receiver, new IntentFilter(
-                DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+                DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED_UNAUDITED);
         return receiver;
     }
 
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 1509ff9..5dbeac2 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -49,7 +49,8 @@
         final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction");
         try {
             for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) {
-                mContext.registerReceiver(new EmptyReceiver(), mockFilter);
+                mContext.registerReceiver(new EmptyReceiver(), mockFilter,
+                        Context.RECEIVER_EXPORTED_UNAUDITED);
             }
             fail("No exception thrown when registering "
                     + (RECEIVER_LIMIT_PER_APP + 1) + " receivers");
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
index ab63f14..86ebdf3 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderEventTest.java
@@ -17,13 +17,18 @@
 package android.service.timezone;
 
 import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -34,12 +39,92 @@
 
 public class TimeZoneProviderEventTest {
 
+    public static final TimeZoneProviderStatus ARBITRARY_TIME_ZONE_PROVIDER_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
+
+    @Test
+    public void createPermanentFailure() {
+        long creationElapsedMillis = 1111L;
+        String cause = "Cause";
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
+                creationElapsedMillis, cause);
+
+        assertEquals(EVENT_TYPE_PERMANENT_FAILURE, event.getType());
+        assertEquals(cause, event.getFailureCause());
+        assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+        assertNull(event.getSuggestion());
+        assertNull(event.getTimeZoneProviderStatus());
+    }
+
+    @Test
+    public void createSuggestion() {
+        long creationElapsedMillis = 1111L;
+        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                .setElapsedRealtimeMillis(2222L)
+                .setTimeZoneIds(Collections.singletonList("Europe/London"))
+                .build();
+
+        TimeZoneProviderStatus reportedStatus = new TimeZoneProviderStatus.Builder()
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                .build();
+
+        assertThrows(NullPointerException.class, () -> TimeZoneProviderEvent.createSuggestionEvent(
+                creationElapsedMillis, /*suggestion=*/null, reportedStatus));
+
+        // Only TimeZoneProvider can report itself certain.
+        {
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+                    creationElapsedMillis, suggestion, reportedStatus);
+            assertEquals(EVENT_TYPE_SUGGESTION, event.getType());
+            assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+            assertNull(event.getFailureCause());
+            assertEquals(suggestion, event.getSuggestion());
+        }
+
+        // Legacy API events can be created where the TimeZoneProviderStatus is omitted.
+        {
+            TimeZoneProviderStatus legacyStatus = null;
+            TimeZoneProviderEvent legacyEvent = TimeZoneProviderEvent.createSuggestionEvent(
+                    creationElapsedMillis, suggestion, legacyStatus);
+            assertEquals(legacyStatus, legacyEvent.getTimeZoneProviderStatus());
+        }
+    }
+
+    @Test
+    public void createUncertain() {
+        long creationElapsedMillis = 1111L;
+
+        // The TimeZoneProvider can report itself uncertain.
+        {
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+                    creationElapsedMillis, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+            assertEquals(EVENT_TYPE_UNCERTAIN, event.getType());
+            assertEquals(creationElapsedMillis, event.getCreationElapsedMillis());
+            assertNull(event.getFailureCause());
+            assertNull(event.getSuggestion());
+        }
+
+        // Legacy API events can be created where the TimeZoneProviderStatus is omitted.
+        {
+            TimeZoneProviderStatus legacyStatus = null;
+            TimeZoneProviderEvent legacyEvent = TimeZoneProviderEvent.createUncertainEvent(
+                    creationElapsedMillis, legacyStatus);
+            assertEquals(legacyStatus, legacyEvent.getTimeZoneProviderStatus());
+        }
+    }
+
     @Test
     public void isEquivalentToAndEquals() {
         long creationElapsedMillis = 1111L;
         TimeZoneProviderEvent failEvent =
                 TimeZoneProviderEvent.createPermanentFailureEvent(creationElapsedMillis, "one");
-        TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+        TimeZoneProviderStatus providerStatus = ARBITRARY_TIME_ZONE_PROVIDER_STATUS;
 
         TimeZoneProviderEvent uncertainEvent =
                 TimeZoneProviderEvent.createUncertainEvent(creationElapsedMillis, providerStatus);
@@ -85,14 +170,14 @@
     @Test
     public void isEquivalentToAndEquals_uncertain() {
         TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                 .build();
 
         TimeZoneProviderEvent uncertain1v1 =
@@ -123,14 +208,14 @@
     @Test
     public void isEquivalentToAndEquals_suggestion() {
         TimeZoneProviderStatus status1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                 .build();
         TimeZoneProviderSuggestion suggestion1 = new TimeZoneProviderSuggestion.Builder()
                 .setElapsedRealtimeMillis(1111L)
@@ -194,7 +279,13 @@
     @Test
     public void testParcelable_uncertain() {
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
-                1111L, TimeZoneProviderStatus.UNKNOWN);
+                1111L, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+        assertRoundTripParcelable(event);
+    }
+
+    @Test
+    public void testParcelable_uncertain_legacy() {
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(1111L, null);
         assertRoundTripParcelable(event);
     }
 
@@ -204,7 +295,17 @@
                 .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
                 .build();
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
-                1111L, suggestion, TimeZoneProviderStatus.UNKNOWN);
+                1111L, suggestion, ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
+        assertRoundTripParcelable(event);
+    }
+
+    @Test
+    public void testParcelable_suggestion_legacy() {
+        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                .setTimeZoneIds(Arrays.asList("Europe/London", "Europe/Paris"))
+                .build();
+        TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+                1111L, suggestion, null);
         assertRoundTripParcelable(event);
     }
 
diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
index d61c33c..b7a595c 100644
--- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
+++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java
@@ -18,9 +18,10 @@
 
 import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -32,33 +33,44 @@
 public class TimeZoneProviderStatusTest {
 
     @Test
+    public void parseProviderStatus() {
+        TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                .build();
+
+        assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString()));
+    }
+
+    @Test
     public void testStatusValidation() {
         TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(DEPENDENCY_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
 
         assertThrows(IllegalArgumentException.class,
                 () -> new TimeZoneProviderStatus.Builder(status)
-                        .setLocationDetectionStatus(-1)
+                        .setLocationDetectionDependencyStatus(-1)
                         .build());
         assertThrows(IllegalArgumentException.class,
                 () -> new TimeZoneProviderStatus.Builder(status)
-                        .setConnectivityStatus(-1)
+                        .setConnectivityDependencyStatus(-1)
                         .build());
         assertThrows(IllegalArgumentException.class,
                 () -> new TimeZoneProviderStatus.Builder(status)
-                        .setTimeZoneResolutionStatus(-1)
+                        .setTimeZoneResolutionOperationStatus(-1)
                         .build());
     }
 
     @Test
     public void testEqualsAndHashcode() {
         TimeZoneProviderStatus status1_1 = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         assertEqualsAndHashcode(status1_1, status1_1);
         assertNotEquals(status1_1, null);
@@ -72,21 +84,21 @@
 
         {
             TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setLocationDetectionStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
                     .build();
             assertNotEquals(status1_1, status2);
         }
 
         {
             TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
                     .build();
             assertNotEquals(status1_1, status2);
         }
 
         {
             TimeZoneProviderStatus status2 = new TimeZoneProviderStatus.Builder(status1_1)
-                    .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                     .build();
             assertNotEquals(status1_1, status2);
         }
@@ -101,9 +113,9 @@
     @Test
     public void testParcelable() {
         TimeZoneProviderStatus status = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                 .build();
         assertRoundTripParcelable(status);
     }
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 44bb062..ddcb175 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -100,12 +100,12 @@
             mImeConsumer.onWindowFocusGained(true);
             mController.show(WindowInsets.Type.ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
 
             // test if setVisibility can hide IME
             mController.hide(WindowInsets.Type.ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0);
         });
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index d0f7fe04..e9cd8ad 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -90,7 +89,6 @@
         mInsetsState = new InsetsState();
         mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100));
         mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
-        doNothing().when(mMockController).onRequestedVisibilityChanged(any());
         InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState,
                 () -> mMockTransaction, mMockController);
         topConsumer.setControl(
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 5e12313..409bae8 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -255,10 +255,7 @@
 
     @Test
     public void testAnimationEndState() {
-        InsetsSourceControl[] controls = prepareControls();
-        InsetsSourceControl navBar = controls[0];
-        InsetsSourceControl statusBar = controls[1];
-        InsetsSourceControl ime = controls[2];
+        prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
@@ -267,16 +264,13 @@
             mController.show(all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            final @InsetsType int types = navigationBars() | statusBars() | ime();
+            assertEquals(types, mController.getRequestedVisibleTypes() & types);
 
             mController.hide(ime(), true /* fromIme */);
             mController.hide(all());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & types);
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -290,10 +284,10 @@
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
             mController.show(ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
             mController.hide(ime(), true /* fromIme */);
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, ime()));
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -307,26 +301,22 @@
         InsetsSourceControl ime = controls[2];
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            int types = navigationBars() | systemBars();
+            int types = navigationBars() | statusBars();
             // test hide select types.
             mController.hide(types);
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(statusBars()));
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
-            // test hide all
+            // test show all
             mController.show(types);
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(types, mController.getRequestedVisibleTypes() & (types | ime()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -339,33 +329,27 @@
         InsetsSourceControl ime = controls[2];
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            int types = navigationBars() | systemBars();
+            int types = navigationBars() | statusBars();
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(types, mController.getRequestedVisibleTypes() & types);
+            assertEquals(0, mController.getRequestedVisibleTypes() & ime());
 
             // test hide all
             mController.hide(all());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
             // test single show
             mController.show(navigationBars());
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(navigationBars(),
+                    mController.getRequestedVisibleTypes() & (types | ime()));
 
             // test single hide
             mController.hide(navigationBars());
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -373,49 +357,38 @@
 
     @Test
     public void testShowHideMultiple() {
-        InsetsSourceControl[] controls = prepareControls();
-        InsetsSourceControl navBar = controls[0];
-        InsetsSourceControl statusBar = controls[1];
-        InsetsSourceControl ime = controls[2];
+        prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // start two animations and see if previous is cancelled and final state is reached.
             mController.hide(navigationBars());
             mController.hide(systemBars());
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            int types = navigationBars() | statusBars();
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
 
             mController.show(navigationBars());
             mController.show(systemBars());
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(types, mController.getRequestedVisibleTypes() & (types | ime()));
 
-            int types = navigationBars() | systemBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(navigationBars());
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(statusBars(), mController.getRequestedVisibleTypes() & (types | ime()));
 
             mController.hide(systemBars());
-            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(navigationBars()));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -428,20 +401,16 @@
         InsetsSourceControl ime = controls[2];
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            int types = navigationBars() | systemBars();
+            int types = navigationBars() | statusBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(navigationBars());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(statusBars(), mController.getRequestedVisibleTypes() & (types | ime()));
 
             mController.hide(systemBars());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+            assertEquals(0, mController.getRequestedVisibleTypes() & (types | ime()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -453,7 +422,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.hide(statusBars());
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
             assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
 
             // Loosing control
@@ -461,14 +430,14 @@
             state.setSourceVisible(ITYPE_STATUS_BAR, true);
             mController.onStateChanged(state);
             mController.onControlsChanged(new InsetsSourceControl[0]);
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
             assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
 
             // Gaining control
             mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
-            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
             assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -488,9 +457,9 @@
             // Gaining control shortly after
             mController.onControlsChanged(createSingletonControl(ITYPE_IME));
 
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
             assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -509,9 +478,9 @@
             // Pretend IME is calling
             mController.show(ime(), true /* fromIme */);
 
-            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
+            assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
-            assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
             assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -538,7 +507,7 @@
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -565,7 +534,7 @@
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, statusBars()));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -701,6 +670,7 @@
 
     private void doTestResizeAnimation_insetsTypes(@InternalInsetsType int type,
             @AnimationType int expectedAnimationType) {
+        final @InsetsType int publicType = InsetsState.toPublicType(type);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final InsetsState state1 = new InsetsState();
             state1.getSource(type).setVisible(true);
@@ -711,15 +681,15 @@
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing frame might cause the resize animation. This depends on the insets type.
             mController.onStateChanged(state2);
-            assertEquals(message, expectedAnimationType, mController.getAnimationType(type));
+            assertEquals(message, expectedAnimationType, mController.getAnimationType(publicType));
 
             // Cancel the existing animations for the next iteration.
             mController.cancelExistingAnimations();
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -728,6 +698,7 @@
     public void testResizeAnimation_displayFrame() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+            final @InsetsType int publicType = statusBars();
             final InsetsState state1 = new InsetsState();
             state1.setDisplayFrame(new Rect(0, 0, 500, 1000));
             state1.getSource(type).setFrame(0, 0, 500, 50);
@@ -738,11 +709,11 @@
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing frame won't cause the resize animation if the display frame is also changed.
             mController.onStateChanged(state2);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -751,6 +722,7 @@
     public void testResizeAnimation_visibility() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final @InternalInsetsType int type = ITYPE_STATUS_BAR;
+            final @InsetsType int publicType = statusBars();
             final InsetsState state1 = new InsetsState();
             state1.getSource(type).setVisible(true);
             state1.getSource(type).setFrame(0, 0, 500, 50);
@@ -764,17 +736,17 @@
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing source visibility (visible --> invisible) won't cause the resize animation.
             // The previous source and the current one must be both visible.
             mController.onStateChanged(state2);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
 
             // Changing source visibility (invisible --> visible) won't cause the resize animation.
             // The previous source and the current one must be both visible.
             mController.onStateChanged(state3);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -940,10 +912,10 @@
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
             mController.show(ime());
-            assertTrue(imeInsetsConsumer.isRequestedVisible());
+            assertTrue(isRequestedVisible(mController, ime()));
 
             mController.hide(ime());
-            assertFalse(imeInsetsConsumer.isRequestedVisible());
+            assertFalse(isRequestedVisible(mController, ime()));
         });
     }
 
@@ -980,6 +952,10 @@
         return controls;
     }
 
+    private static boolean isRequestedVisible(InsetsController controller, @InsetsType int type) {
+        return (controller.getRequestedVisibleTypes() & type) != 0;
+    }
+
     public static class TestHost extends ViewRootInsetsControllerHost {
 
         private @InsetsType int mRequestedVisibleTypes = defaultVisible();
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 8cf118c..1253278 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -122,7 +122,6 @@
     public void testHide() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mConsumer.hide();
-            assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
             verify(mSpyInsetsSource).setVisible(eq(false));
         });
 
@@ -134,7 +133,6 @@
             // Insets source starts out visible
             mConsumer.hide();
             mConsumer.show(false /* fromIme */);
-            assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
             verify(mSpyInsetsSource).setVisible(eq(false));
             verify(mSpyInsetsSource).setVisible(eq(true));
         });
@@ -240,7 +238,7 @@
             // visibility won't be updated when the consumer received the same leash in setControl.
             insetsController.controlWindowInsetsAnimation(ime(), 0L,
                     null /* interpolator */, null /* cancellationSignal */, null /* listener */);
-            assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+            assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
             imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
                     true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
             verify(mMockTransaction, never()).show(mLeash);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 99670d9..32085c1 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 40;
+    private static final int NUM_MARSHALLED_PROPERTIES = 41;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 599cf97..6e73b9f 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -52,6 +52,8 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Supplemental tests that cannot be covered by CTS (e.g. due to hidden API dependencies).
@@ -500,6 +502,7 @@
                 + "prefix: extras=null\n"
                 + "prefix: hintLocales=null\n"
                 + "prefix: supportedHandwritingGestureTypes=(none)\n"
+                + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
                 + "prefix: contentMimeTypes=null\n");
     }
 
@@ -516,6 +519,8 @@
         info.label = "testLabel";
         info.setInitialToolType(MotionEvent.TOOL_TYPE_STYLUS);
         info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class));
+        info.setSupportedHandwritingGesturePreviews(
+                Stream.of(SelectGesture.class).collect(Collectors.toSet()));
         info.packageName = "android.view.inputmethod";
         info.autofillId = new AutofillId(123);
         info.fieldId = 456;
@@ -538,6 +543,7 @@
                         + "prefix2: extras=Bundle[{testKey=testValue}]\n"
                         + "prefix2: hintLocales=[en,es,zh]\n"
                         + "prefix2: supportedHandwritingGestureTypes=SELECT\n"
+                        + "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n"
                         + "prefix2: contentMimeTypes=[image/png]\n"
                         + "prefix2: targetInputMethodUserId=10\n");
     }
@@ -558,6 +564,7 @@
                         + "prefix: packageName=null autofillId=null fieldId=0 fieldName=null\n"
                         + "prefix: hintLocales=null\n"
                         + "prefix: supportedHandwritingGestureTypes=(none)\n"
+                        + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
                         + "prefix: contentMimeTypes=null\n");
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
new file mode 100644
index 0000000..f111bf6f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputMethodSubtypeHandleTest {
+
+    @Test
+    public void testCreateFromRawHandle() {
+        {
+            final InputMethodSubtypeHandle handle =
+                    InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(""));
+
+        // The IME ID must use ComponentName#flattenToShortString(), not #flattenToString().
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/com.android.test.Ime1:subtype:1"));
+
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:0001"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1!"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1:"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1:2"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:a"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:0x01"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:Subtype:a"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "ime1:subtype:1"));
+    }
+
+    @Test
+    public void testCreateFromInputMethodInfo() {
+        final InputMethodInfo imi = new InputMethodInfo(
+                "com.android.test", "com.android.test.Ime1", "TestIME", null);
+        {
+            final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, null);
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:0", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        final InputMethodSubtype subtype =
+                new InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(1).build();
+        {
+            final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, subtype);
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, null));
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, subtype));
+    }
+
+    @Test
+    public void testEquality() {
+        assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"));
+        assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode(),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode());
+
+        assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:2"));
+        assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime2:subtype:1"));
+    }
+
+    @Test
+    public void testParcelablility() {
+        final InputMethodSubtypeHandle original =
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+        final InputMethodSubtypeHandle cloned = cloneHandle(original);
+        assertEquals(original, cloned);
+        assertEquals(original.hashCode(), cloned.hashCode());
+        assertEquals(original.getComponentName(), cloned.getComponentName());
+        assertEquals(original.getImeId(), cloned.getImeId());
+        assertEquals(original.toStringHandle(), cloned.toStringHandle());
+    }
+
+    @Test
+    public void testNoUnnecessaryStringInstantiationInToStringHandle() {
+        final String validHandleStr = "com.android.test/.Ime1:subtype:1";
+        // Verify that toStringHandle() returns the same String object if the input is valid for
+        // an efficient memory usage.
+        assertSame(validHandleStr, InputMethodSubtypeHandle.of(validHandleStr).toStringHandle());
+    }
+
+    @NonNull
+    private static InputMethodSubtypeHandle cloneHandle(
+            @NonNull InputMethodSubtypeHandle original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return InputMethodSubtypeHandle.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index c53fb23..048c48b 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -25,7 +25,7 @@
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -65,7 +65,7 @@
                 new Binder() /* imeToken */,
                 true /* navbarColorManagedByIme */,
                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                new InsetsVisibilities() /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 "test" /* packageName */,
                 new int[0] /* transientBarTypes */,
                 new LetterboxDetails[] {letterboxDetails});
@@ -87,7 +87,7 @@
         assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
         assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
         assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
-        assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
+        assertThat(copy.mRequestedVisibleTypes).isEqualTo(original.mRequestedVisibleTypes);
         assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
         assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
         assertThat(copy.mLetterboxDetails).isEqualTo(original.mLetterboxDetails);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ca543f4..85c6beb 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1951,6 +1951,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "-240296576": {
+      "message": "handleAppTransitionReady: displayId=%d appTransition={%s} openingApps=[%s] closingApps=[%s] transit=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "-237664290": {
       "message": "Pause the recording session on display %s",
       "level": "VERBOSE",
@@ -2053,12 +2059,6 @@
       "group": "WM_DEBUG_CONTENT_RECORDING",
       "at": "com\/android\/server\/wm\/ContentRecorder.java"
     },
-    "-134793542": {
-      "message": "handleAppTransitionReady: displayId=%d appTransition={%s} excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/AppTransitionController.java"
-    },
     "-134091882": {
       "message": "Screenshotting Activity %s",
       "level": "VERBOSE",
@@ -2599,6 +2599,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "323235828": {
+      "message": "Delaying app transition for recents animation to finish",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "327461496": {
       "message": "Complete pause: %s",
       "level": "VERBOSE",
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index dbd918e..6245598 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -30,6 +30,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
 import java.util.Collection;
 import java.util.Locale;
@@ -914,6 +915,51 @@
     }
 
     /**
+     * @hide
+     */
+    public abstract static class EcCurve {
+        private EcCurve() {}
+
+        /**
+         * @hide
+         */
+        public static int toKeymasterCurve(ECParameterSpec spec) {
+            int keySize = spec.getCurve().getField().getFieldSize();
+            switch (keySize) {
+                case 224:
+                    return android.hardware.security.keymint.EcCurve.P_224;
+                case 256:
+                    return android.hardware.security.keymint.EcCurve.P_256;
+                case 384:
+                    return android.hardware.security.keymint.EcCurve.P_384;
+                case 521:
+                    return android.hardware.security.keymint.EcCurve.P_521;
+                default:
+                    return -1;
+            }
+        }
+
+        /**
+         * @hide
+         */
+        public static int fromKeymasterCurve(int ecCurve) {
+            switch (ecCurve) {
+                case android.hardware.security.keymint.EcCurve.P_224:
+                    return 224;
+                case android.hardware.security.keymint.EcCurve.P_256:
+                case android.hardware.security.keymint.EcCurve.CURVE_25519:
+                    return 256;
+                case android.hardware.security.keymint.EcCurve.P_384:
+                    return 384;
+                case android.hardware.security.keymint.EcCurve.P_521:
+                    return 521;
+                default:
+                    return -1;
+            }
+        }
+    }
+
+    /**
      * Namespaces provide system developers and vendors with a way to use keystore without
      * requiring an applications uid. Namespaces can be configured using SEPolicy.
      * See <a href="https://source.android.com/security/keystore#access-control">
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
index 5216a90..ace2053 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -203,6 +203,11 @@
         for (Authorization a : key.getAuthorizations()) {
             if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) {
                 keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a);
+                break;
+            } else if (a.keyParameter.tag == KeymasterDefs.KM_TAG_EC_CURVE) {
+                keySizeBits = KeyProperties.EcCurve.fromKeymasterCurve(
+                        a.keyParameter.value.getEcCurve());
+                break;
             }
         }
 
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 94bf122..7a320ba 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -66,6 +66,7 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.ECKey;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -566,6 +567,22 @@
                         spec.getMaxUsageCount()
                 ));
             }
+            if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) {
+                if (key instanceof ECKey) {
+                    ECKey ecKey = (ECKey) key;
+                    importArgs.add(KeyStore2ParameterUtils.makeEnum(
+                            KeymasterDefs.KM_TAG_EC_CURVE,
+                            KeyProperties.EcCurve.toKeymasterCurve(ecKey.getParams())
+                    ));
+                }
+            }
+            /* TODO: check for Ed25519(EdDSA) or X25519(XDH) key algorithm and
+             *  add import args for KM_TAG_EC_CURVE as EcCurve.CURVE_25519.
+             *  Currently conscrypt does not support EdDSA key import and XDH keys are not an
+             *  instance of XECKey, hence these conditions are not added, once it is fully
+             *  implemented by conscrypt, we can add CURVE_25519 argument for EdDSA and XDH
+             *  algorithms.
+             */
         } catch (IllegalArgumentException | IllegalStateException e) {
             throw new KeyStoreException(e);
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
         @Override
         public void onActivityPreCreated(@NonNull Activity activity,
                 @Nullable Bundle savedInstanceState) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 final IBinder activityToken = activity.getActivityToken();
                 final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
         @Override
         public void onActivityPostCreated(@NonNull Activity activity,
                 @Nullable Bundle savedInstanceState) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             // Calling after Activity#onCreate is complete to allow the app launch something
             // first. In case of a configured placeholder activity we want to make sure
             // that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
 
         @Override
         public void onActivityConfigurationChanged(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 final TransactionRecord transactionRecord = mTransactionManager
                         .startNewTransaction();
@@ -1934,6 +1949,11 @@
 
         @Override
         public void onActivityPostDestroyed(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 SplitController.this.onActivityDestroyed(activity);
             }
@@ -1969,7 +1989,11 @@
             if (who instanceof Activity) {
                 // We will check if the new activity should be split with the activity that launched
                 // it.
-                launchingActivity = (Activity) who;
+                final Activity activity = (Activity) who;
+                // For Activity that is child of another Activity (ActivityGroup), treat the parent
+                // Activity as the launching one because it's window will just be a child of the
+                // parent Activity window.
+                launchingActivity = activity.isChild() ? activity.getParent() : activity;
                 if (isInPictureInPicture(launchingActivity)) {
                     // We don't embed activity when it is in PIP.
                     return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
         final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
                 secondaryActivity);
         TaskFragmentContainer containerToAvoid = primaryContainer;
-        if (curSecondaryContainer != null
+        if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
                 && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
             // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
             // the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index dc5b9a1e..64220c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -38,6 +38,7 @@
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowFocusObserver;
 import android.view.InputDevice;
@@ -187,6 +188,31 @@
         }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
+
+        initBackAnimationRunners();
+    }
+
+    private void initBackAnimationRunners() {
+        final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
+        final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
+            @Override
+            public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                    IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                // Animation missing. Simply finish animation.
+                finishedCallback.onAnimationFinished();
+            }
+        };
+
+        final BackAnimationRunner dummyBackRunner =
+                new BackAnimationRunner(dummyCallback, dummyRunner);
+        final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+                new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
+        // TODO (238474994): register cross activity animation when it's completed.
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
+        // TODO (236760237): register dialog close animation when it's completed.
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
new file mode 100644
index 0000000..2074b6a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the animation of swiping back and returning to another task.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows.
+ * Once the gesture is committed, the second part remains the closing window in place.
+ * The entering window plays the rest of app opening transition to enter full screen.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ */
+@ShellMainThread
+class CrossTaskBackAnimation {
+    private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+
+    /**
+     * Minimum scale of the entering window.
+     */
+    private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+
+    /**
+     * Minimum scale of the closing window.
+     */
+    private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
+
+    /**
+     * Minimum color scale of the closing window.
+     */
+    private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
+
+    /**
+     * The margin between the entering window and the closing window
+     */
+    private static final int WINDOW_MARGIN = 35;
+
+    /** Max window translation in the Y axis. */
+    private static final int WINDOW_MAX_DELTA_Y = 160;
+
+    private final Rect mStartTaskRect = new Rect();
+    private final float mCornerRadius;
+
+    // The closing window properties.
+    private final RectF mClosingCurrentRect = new RectF();
+
+    // The entering window properties.
+    private final Rect mEnteringStartRect = new Rect();
+    private final RectF mEnteringCurrentRect = new RectF();
+
+    private final PointF mInitialTouchPos = new PointF();
+    private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+    private final Matrix mTransformMatrix = new Matrix();
+
+    private final float[] mTmpFloat9 = new float[9];
+    private final float[] mTmpTranslate = {0, 0, 0};
+
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private SurfaceControl mBackgroundSurface;
+    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private boolean mBackInProgress = false;
+
+    private boolean mIsRightEdge;
+    private float mProgress = 0;
+    private PointF mTouchPos = new PointF();
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+
+    private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+
+    final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossTaskBackAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    };
+
+    final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
+        @Override
+        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+    };
+
+    CrossTaskBackAnimation(Context context) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+    }
+
+    private float getInterpolatedProgress(float backProgress) {
+        return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+
+        // Offset start rectangle to align task bounds.
+        mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+        mStartTaskRect.offsetTo(0, 0);
+
+        // Draw background.
+        mBackgroundSurface = new SurfaceControl.Builder()
+                .setName("Background of Back Navigation")
+                .setColorLayer()
+                .setHidden(false)
+                .build();
+        mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
+                .setLayer(mBackgroundSurface, -1);
+        mTransaction.apply();
+    }
+
+    private void updateGestureBackProgress(float progress, BackEvent event) {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            return;
+        }
+
+        float touchX = event.getTouchX();
+        float touchY = event.getTouchY();
+        float dX = Math.abs(touchX - mInitialTouchPos.x);
+
+        // The 'follow width' is the width of the window if it completely matches
+        // the gesture displacement.
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        // The 'progress width' is the width of the window if it strictly linearly interpolates
+        // to minimum scale base on progress.
+        float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
+        float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
+        float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+
+        // The final width is derived from interpolating between the follow with and progress width
+        // using gesture progress.
+        float enteringWidth = enteringScale * width;
+        float closingWidth = closingScale * width;
+        float enteringHeight = (float) height / width * enteringWidth;
+        float closingHeight = (float) height / width * closingWidth;
+
+        float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        // Base the window movement in the Y axis on the touch movement in the Y axis.
+        float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+        // Move the window along the Y axis.
+        float closingTop = (height - closingHeight) * 0.5f + deltaY;
+        float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
+        // Move the window along the X axis.
+        float right = width - (progress * WINDOW_MARGIN);
+        float left = right - closingWidth;
+
+        mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
+        mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
+                left - WINDOW_MARGIN, enteringTop + enteringHeight);
+
+        applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
+        applyColorTransform(mClosingTarget.leash, closingColorScale);
+        applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+        mTransaction.apply();
+    }
+
+    private void updatePostCommitClosingAnimation(float progress) {
+        mTransaction.setLayer(mClosingTarget.leash, 0);
+        float alpha = mapRange(progress, 1, 0);
+        mTransaction.setAlpha(mClosingTarget.leash, alpha);
+    }
+
+    private void updatePostCommitEnteringAnimation(float progress) {
+        float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+        float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+        float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+        float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+
+        mEnteringCurrentRect.set(left, top, left + width, top + height);
+        applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+    }
+
+    /** Transform the target window to match the target rect. */
+    private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
+        if (leash == null) {
+            return;
+        }
+
+        final float scale = targetRect.width() / mStartTaskRect.width();
+        mTransformMatrix.reset();
+        mTransformMatrix.setScale(scale, scale);
+        mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+        mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9)
+                .setWindowCrop(leash, mStartTaskRect)
+                .setCornerRadius(leash, cornerRadius);
+    }
+
+    private void applyColorTransform(SurfaceControl leash, float colorScale) {
+        if (leash == null) {
+            return;
+        }
+        computeScaleTransformMatrix(colorScale, mTmpFloat9);
+        mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
+    }
+
+    static void computeScaleTransformMatrix(float scale, float[] matrix) {
+        matrix[0] = scale;
+        matrix[1] = 0;
+        matrix[2] = 0;
+        matrix[3] = 0;
+        matrix[4] = scale;
+        matrix[5] = 0;
+        matrix[6] = 0;
+        matrix[7] = 0;
+        matrix[8] = scale;
+    }
+
+    private void finishAnimation() {
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+
+        if (mBackgroundSurface != null) {
+            mBackgroundSurface.release();
+            mBackgroundSurface = null;
+        }
+
+        mBackInProgress = false;
+        mTransformMatrix.reset();
+        mClosingCurrentRect.setEmpty();
+        mInitialTouchPos.set(0, 0);
+
+        if (mFinishCallback != null) {
+            try {
+                mFinishCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            mFinishCallback = null;
+        }
+    }
+
+    private void onGestureProgress(@NonNull BackEvent backEvent) {
+        if (!mBackInProgress) {
+            mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+            mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+            mBackInProgress = true;
+        }
+        mProgress = backEvent.getProgress();
+        mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+        updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+    }
+
+    private void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            finishAnimation();
+            return;
+        }
+
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase.
+        mEnteringCurrentRect.round(mEnteringStartRect);
+
+        ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
+        valueAnimator.setInterpolator(mInterpolator);
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            updatePostCommitEnteringAnimation(progress);
+            updatePostCommitClosingAnimation(progress);
+            mTransaction.apply();
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3972b59..a400555 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -24,6 +24,7 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -37,7 +38,6 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
-import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -150,6 +150,9 @@
 
     private final ShellExecutor mBackgroundExecutor;
 
+    // Whether or not we should show bubbles pinned at the bottom of the screen.
+    private boolean mIsBubbleBarEnabled;
+
     private BubbleLogger mLogger;
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
@@ -210,7 +213,6 @@
     /** Drag and drop controller to register listener for onDragStarted. */
     private DragAndDropController mDragAndDropController;
 
-  
     public BubbleController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
@@ -526,6 +528,12 @@
         mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
     }
 
+    // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
+    /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
+    public void setBubbleBarEnabled(boolean enabled) {
+        mIsBubbleBarEnabled = enabled;
+    }
+
     /** Whether this userId belongs to the current user. */
     private boolean isCurrentProfile(int userId) {
         return userId == UserHandle.USER_ALL
@@ -591,7 +599,8 @@
             }
             mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
         }
-        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
+
+        if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
             mBubblePositioner.setUsePinnedLocation(true);
         } else {
             mBubblePositioner.setUsePinnedLocation(false);
@@ -967,14 +976,18 @@
     }
 
     /**
-     * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification
-     * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble
-     * is supported at a time.
+     * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n
+     * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
+     * bubble is supported at a time.
      *
      * @param intent the intent to display in the bubble expanded view.
      */
-    public void addAppBubble(Intent intent) {
+    public void showAppBubble(Intent intent) {
         if (intent == null || intent.getPackage() == null) return;
+
+        PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+        if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
+
         Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
         b.setShouldAutoExpand(true);
         inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
@@ -1497,18 +1510,23 @@
         }
         PackageManager packageManager = getPackageManagerForUser(
                 context, entry.getStatusBarNotification().getUser().getIdentifier());
-        ActivityInfo info =
-                intent.getIntent().resolveActivityInfo(packageManager, 0);
+        return isResizableActivity(intent.getIntent(), packageManager, entry.getKey());
+    }
+
+    static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) {
+        if (intent == null) {
+            Log.w(TAG, "Unable to send as bubble: " + key + " null intent");
+            return false;
+        }
+        ActivityInfo info = intent.resolveActivityInfo(packageManager, 0);
         if (info == null) {
-            Log.w(TAG, "Unable to send as bubble, "
-                    + entry.getKey() + " couldn't find activity info for intent: "
-                    + intent);
+            Log.w(TAG, "Unable to send as bubble: " + key
+                    + " couldn't find activity info for intent: " + intent);
             return false;
         }
         if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
-            Log.w(TAG, "Unable to send as bubble, "
-                    + entry.getKey() + " activity is not resizable for intent: "
-                    + intent);
+            Log.w(TAG, "Unable to send as bubble: " + key
+                    + " activity is not resizable for intent: " + intent);
             return false;
         }
         return true;
@@ -1682,6 +1700,13 @@
         }
 
         @Override
+        public void showAppBubble(Intent intent) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.showAppBubble(intent);
+            });
+        }
+
+        @Override
         public boolean handleDismissalInterception(BubbleEntry entry,
                 @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
                 Executor callbackExecutor) {
@@ -1792,6 +1817,13 @@
         }
 
         @Override
+        public void setBubbleBarEnabled(boolean enabled) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.setBubbleBarEnabled(enabled);
+            });
+        }
+
+        @Override
         public void onNotificationPanelExpandedChanged(boolean expanded) {
             mMainExecutor.execute(
                     () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f31a27d..f621351 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -83,7 +83,6 @@
 import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
 import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController;
 import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl;
-import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub;
 import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -105,11 +104,6 @@
  */
 public class BubbleStackView extends FrameLayout
         implements ViewTreeObserver.OnComputeInternalInsetsListener {
-    /**
-     * Set to {@code true} to enable home gesture handling in bubbles
-     */
-    public static final boolean HOME_GESTURE_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
 
     public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
             SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
@@ -898,12 +892,8 @@
         mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
                 onBubbleAnimatedOut, this);
 
-        if (HOME_GESTURE_ENABLED) {
-            mExpandedViewAnimationController =
-                    new ExpandedViewAnimationControllerImpl(context, mPositioner);
-        } else {
-            mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub();
-        }
+        mExpandedViewAnimationController =
+                new ExpandedViewAnimationControllerImpl(context, mPositioner);
 
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
@@ -1964,11 +1954,7 @@
 
         if (wasExpanded) {
             stopMonitoringSwipeUpGesture();
-            if (HOME_GESTURE_ENABLED) {
-                animateCollapse();
-            } else {
-                animateCollapseWithScale();
-            }
+            animateCollapse();
             logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
             animateExpansion();
@@ -1976,13 +1962,11 @@
             logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
             logBubbleEvent(mExpandedBubble,
                     FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
-            if (HOME_GESTURE_ENABLED) {
-                mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
-                    if (!notifPanelExpanded && mIsExpanded) {
-                        startMonitoringSwipeUpGesture();
-                    }
-                });
-            }
+            mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+                if (!notifPanelExpanded && mIsExpanded) {
+                    startMonitoringSwipeUpGesture();
+                }
+            });
         }
         notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
@@ -2298,106 +2282,6 @@
         mMainExecutor.executeDelayed(mDelayedAnimation, startDelay);
     }
 
-    private void animateCollapseWithScale() {
-        cancelDelayedExpandCollapseSwitchAnimations();
-
-        if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
-            mManageEduView.hide();
-        }
-        // Hide the menu if it's visible.
-        showManageMenu(false);
-
-        mIsExpanded = false;
-        mIsExpansionAnimating = true;
-
-        showScrim(false);
-
-        mBubbleContainer.cancelAllAnimations();
-
-        // If we were in the middle of swapping, the animating-out surface would have been scaling
-        // to zero - finish it off.
-        PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
-        mAnimatingOutSurfaceContainer.setScaleX(0f);
-        mAnimatingOutSurfaceContainer.setScaleY(0f);
-
-        // Let the expanded animation controller know that it shouldn't animate child adds/reorders
-        // since we're about to animate collapsed.
-        mExpandedAnimationController.notifyPreparingToCollapse();
-
-        mExpandedAnimationController.collapseBackToStack(
-                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
-                /* collapseTo */,
-                () -> mBubbleContainer.setActiveController(mStackAnimationController));
-
-        int index;
-        if (mExpandedBubble != null && BubbleOverflow.KEY.equals(mExpandedBubble.getKey())) {
-            index = mBubbleData.getBubbles().size();
-        } else {
-            index = mBubbleData.getBubbles().indexOf(mExpandedBubble);
-        }
-        // Value the bubble is animating from (back into the stack).
-        final PointF p = mPositioner.getExpandedBubbleXY(index, getState());
-        if (mPositioner.showBubblesVertically()) {
-            float pivotX;
-            float pivotY = p.y + mBubbleSize / 2f;
-            if (mStackOnLeftOrWillBe) {
-                pivotX = mPositioner.getAvailableRect().left + mBubbleSize + mExpandedViewPadding;
-            } else {
-                pivotX = mPositioner.getAvailableRect().right - mBubbleSize - mExpandedViewPadding;
-            }
-            mExpandedViewContainerMatrix.setScale(
-                    1f, 1f,
-                    pivotX, pivotY);
-        } else {
-            mExpandedViewContainerMatrix.setScale(
-                    1f, 1f,
-                    p.x + mBubbleSize / 2f,
-                    p.y + mBubbleSize + mExpandedViewPadding);
-        }
-
-        mExpandedViewAlphaAnimator.reverse();
-
-        // When the animation completes, we should no longer be showing the content.
-        if (mExpandedBubble.getExpandedView() != null) {
-            mExpandedBubble.getExpandedView().setContentVisibility(false);
-        }
-
-        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
-        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
-                .spring(AnimatableScaleMatrix.SCALE_X,
-                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
-                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
-                        mScaleOutSpringConfig)
-                .spring(AnimatableScaleMatrix.SCALE_Y,
-                        AnimatableScaleMatrix.getAnimatableValueForScaleFactor(
-                                1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT),
-                        mScaleOutSpringConfig)
-                .addUpdateListener((target, values) -> {
-                    mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-                })
-                .withEndActions(() -> {
-                    final BubbleViewProvider previouslySelected = mExpandedBubble;
-                    beforeExpandedViewAnimation();
-                    if (mManageEduView != null) {
-                        mManageEduView.hide();
-                    }
-
-                    if (DEBUG_BUBBLE_STACK_VIEW) {
-                        Log.d(TAG, "animateCollapse");
-                        Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
-                                mExpandedBubble));
-                    }
-                    updateOverflowVisibility();
-                    updateZOrder();
-                    updateBadges(true /* setBadgeForCollapsedStack */);
-                    afterExpandedViewAnimation();
-                    if (previouslySelected != null) {
-                        previouslySelected.setTaskViewVisibility(false);
-                    }
-                })
-                .start();
-    }
-
     private void animateCollapse() {
         cancelDelayedExpandCollapseSwitchAnimations();
 
@@ -2579,65 +2463,6 @@
      * and clip the expanded view.
      */
     public void setImeVisible(boolean visible) {
-        if (HOME_GESTURE_ENABLED) {
-            setImeVisibleInternal(visible);
-        } else {
-            setImeVisibleWithoutClipping(visible);
-        }
-    }
-
-    private void setImeVisibleWithoutClipping(boolean visible) {
-        if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
-            // This will update the animation so the bubbles move to position for the IME
-            mExpandedAnimationController.expandFromStack(() -> {
-                updatePointerPosition(false /* forIme */);
-                afterExpandedViewAnimation();
-            } /* after */);
-            return;
-        }
-
-        if (!mIsExpanded && getBubbleCount() > 0) {
-            final float stackDestinationY =
-                    mStackAnimationController.animateForImeVisibility(visible);
-
-            // How far the stack is animating due to IME, we'll just animate the flyout by that
-            // much too.
-            final float stackDy =
-                    stackDestinationY - mStackAnimationController.getStackPosition().y;
-
-            // If the flyout is visible, translate it along with the bubble stack.
-            if (mFlyout.getVisibility() == VISIBLE) {
-                PhysicsAnimator.getInstance(mFlyout)
-                        .spring(DynamicAnimation.TRANSLATION_Y,
-                                mFlyout.getTranslationY() + stackDy,
-                                FLYOUT_IME_ANIMATION_SPRING_CONFIG)
-                        .start();
-            }
-        } else if (mPositioner.showBubblesVertically() && mIsExpanded
-                && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-            float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
-                    getState()).y;
-            float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
-            mExpandedBubble.getExpandedView().setImeVisible(visible);
-            if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
-                mExpandedViewContainer.animate().translationY(newExpandedViewTop);
-            }
-
-            List<Animator> animList = new ArrayList();
-            for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-                View child = mBubbleContainer.getChildAt(i);
-                float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
-                ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
-                animList.add(anim);
-            }
-            updatePointerPosition(true /* forIme */);
-            AnimatorSet set = new AnimatorSet();
-            set.playTogether(animList);
-            set.start();
-        }
-    }
-
-    private void setImeVisibleInternal(boolean visible) {
         if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
             // This will update the animation so the bubbles move to position for the IME
             mExpandedAnimationController.expandFromStack(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 7f891ec..465d1ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -22,6 +22,7 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.app.NotificationChannel;
+import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
@@ -108,6 +109,15 @@
     void expandStackAndSelectBubble(Bubble bubble);
 
     /**
+     * Adds and expands bubble that is not notification based, but instead based on an intent from
+     * the app. The intent must be explicit (i.e. include a package name or fully qualified
+     * component class name) and the activity for it should be resizable.
+     *
+     * @param intent the intent to populate the bubble.
+     */
+    void showAppBubble(Intent intent);
+
+    /**
      * @return a bubble that matches the provided shortcutId, if one exists.
      */
     @Nullable
@@ -232,6 +242,11 @@
      */
     void onUserRemoved(int removedUserId);
 
+    /**
+     * Sets whether bubble bar should be enabled or not.
+     */
+    void setBubbleBarEnabled(boolean enabled);
+
     /** Listener to find out about stack expansion / collapse events. */
     interface BubbleExpandListener {
         /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index b91062f..33629f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -20,7 +20,6 @@
 
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
 import static com.android.wm.shell.bubbles.BubbleStackView.ENABLE_FLING_TO_DISMISS_BUBBLE;
-import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED;
 
 import android.content.res.Resources;
 import android.graphics.Path;
@@ -81,11 +80,6 @@
             new PhysicsAnimator.SpringConfig(
                     EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
 
-    private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture =
-            new PhysicsAnimator.SpringConfig(
-                    EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE,
-                    SpringForce.DAMPING_RATIO_NO_BOUNCY);
-
     /** Horizontal offset between bubbles, which we need to know to re-stack them. */
     private float mStackOffsetPx;
     /** Size of each bubble. */
@@ -307,14 +301,8 @@
                     (firstBubbleLeads && index == 0)
                             || (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
 
-            Interpolator interpolator;
-            if (HOME_GESTURE_ENABLED) {
-                // When home gesture is enabled, we use a different animation timing for collapse
-                interpolator = expanding
-                        ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
-            } else {
-                interpolator = Interpolators.LINEAR;
-            }
+            Interpolator interpolator = expanding
+                    ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
 
             animation
                     .followAnimatedTargetAlongPath(
@@ -564,16 +552,10 @@
             finishRemoval.run();
             mOnBubbleAnimatedOutAction.run();
         } else {
-            PhysicsAnimator.SpringConfig springConfig;
-            if (HOME_GESTURE_ENABLED) {
-                springConfig = mAnimateOutSpringConfig;
-            } else {
-                springConfig = mAnimateOutSpringConfigWithoutHomeGesture;
-            }
             PhysicsAnimator.getInstance(child)
                     .spring(DynamicAnimation.ALPHA, 0f)
-                    .spring(DynamicAnimation.SCALE_X, 0f, springConfig)
-                    .spring(DynamicAnimation.SCALE_Y, 0f, springConfig)
+                    .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
+                    .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
                     .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
                     .start();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
deleted file mode 100644
index bb8a3aa..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
+++ /dev/null
@@ -1,57 +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.wm.shell.bubbles.animation;
-
-import com.android.wm.shell.bubbles.BubbleExpandedView;
-
-/**
- * Stub implementation {@link ExpandedViewAnimationController} that does not animate the
- * {@link BubbleExpandedView}
- */
-public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController {
-    @Override
-    public void setExpandedView(BubbleExpandedView expandedView) {
-    }
-
-    @Override
-    public void updateDrag(float distance) {
-    }
-
-    @Override
-    public void setSwipeVelocity(float velocity) {
-    }
-
-    @Override
-    public boolean shouldCollapse() {
-        return false;
-    }
-
-    @Override
-    public void animateCollapse(Runnable startStackCollapse, Runnable after) {
-    }
-
-    @Override
-    public void animateBackToExpanded() {
-    }
-
-    @Override
-    public void animateForImeVisibilityChange(boolean visible) {
-    }
-
-    @Override
-    public void reset() {
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b8e192..1977dcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
-import android.view.WindowManager;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
@@ -65,8 +64,6 @@
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.floating.FloatingTasksController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -576,47 +573,6 @@
     }
 
     //
-    // Floating tasks
-    //
-
-    @WMSingleton
-    @Provides
-    static Optional<FloatingTasks> provideFloatingTasks(
-            Optional<FloatingTasksController> floatingTaskController) {
-        return floatingTaskController.map((controller) -> controller.asFloatingTasks());
-    }
-
-    @WMSingleton
-    @Provides
-    static Optional<FloatingTasksController> provideFloatingTasksController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellCommandHandler shellCommandHandler,
-            Optional<BubbleController> bubbleController,
-            WindowManager windowManager,
-            ShellTaskOrganizer organizer,
-            TaskViewTransitions taskViewTransitions,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellBackgroundThread ShellExecutor bgExecutor,
-            SyncTransactionQueue syncQueue) {
-        if (FloatingTasksController.FLOATING_TASKS_ENABLED) {
-            return Optional.of(new FloatingTasksController(context,
-                    shellInit,
-                    shellController,
-                    shellCommandHandler,
-                    bubbleController,
-                    windowManager,
-                    organizer,
-                    taskViewTransitions,
-                    mainExecutor,
-                    bgExecutor,
-                    syncQueue));
-        } else {
-            return Optional.empty();
-        }
-    }
-
-    //
     // Starting window
     //
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 497a6f6..55378a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -311,7 +311,7 @@
                     animateSplitContainers(true, null /* animCompleteCallback */);
                     animateHighlight(target);
                 }
-            } else {
+            } else if (mCurrentTarget.type != target.type) {
                 // Switching between targets
                 mDropZoneView1.animateSwitch();
                 mDropZoneView2.animateSwitch();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
deleted file mode 100644
index 83a1734..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
+++ /dev/null
@@ -1,259 +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.wm.shell.floating;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-
-import java.util.Objects;
-
-/**
- * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved
- * close to the target to be stuck to it unless moved out again.
- */
-public class FloatingDismissController {
-
-    /** Velocity required to dismiss the view without dragging it into the dismiss target. */
-    private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
-    /**
-     * Max velocity that the view can be moving through the target with to stick (i.e. if it's
-     * more than this velocity, it will pass through the target.
-     */
-    private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f;
-    /**
-     * Percentage of the target width to use to determine if an object flung towards the target
-     * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a
-     * 200px-wide area around the target will be considered 'near' enough get dismissed).
-     */
-    private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f;
-    /** Minimum alpha to apply to the view being dismissed when it is in the target. */
-    private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f;
-    /** Amount to scale down the view being dismissed when it is in the target. */
-    private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f;
-
-    private Context mContext;
-    private FloatingTasksController mController;
-    private FloatingTaskLayer mParent;
-
-    private DismissView mDismissView;
-    private ValueAnimator mDismissAnimator;
-    private View mViewBeingDismissed;
-    private float mDismissSizePercent;
-    private float mDismissSize;
-
-    /**
-     * The currently magnetized object, which is being dragged and will be attracted to the magnetic
-     * dismiss target.
-     */
-    private MagnetizedObject<View> mMagnetizedObject;
-    /**
-     * The MagneticTarget instance for our circular dismiss view. This is added to the
-     * MagnetizedObject instances for the view being dragged.
-     */
-    private MagnetizedObject.MagneticTarget mMagneticTarget;
-    /** Magnet listener that handles animating and dismissing the view. */
-    private MagnetizedObject.MagnetListener mFloatingViewMagnetListener;
-
-    public FloatingDismissController(Context context, FloatingTasksController controller,
-            FloatingTaskLayer parent) {
-        mContext = context;
-        mController = controller;
-        mParent = parent;
-        updateSizes();
-        createAndAddDismissView();
-
-        mDismissAnimator = ValueAnimator.ofFloat(1f, 0f);
-        mDismissAnimator.addUpdateListener(animation -> {
-            final float value = (float) animation.getAnimatedValue();
-            if (mDismissView != null) {
-                mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f);
-                mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f);
-                final float scaleValue = Math.max(value, mDismissSizePercent);
-                mDismissView.getCircle().setScaleX(scaleValue);
-                mDismissView.getCircle().setScaleY(scaleValue);
-            }
-            if (mViewBeingDismissed != null) {
-                // TODO: alpha doesn't actually apply to taskView currently.
-                mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA));
-                mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
-                mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
-            }
-        });
-
-        mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() {
-            @Override
-            public void onStuckToTarget(
-                    @NonNull MagnetizedObject.MagneticTarget target) {
-                animateDismissing(/* dismissing= */ true);
-            }
-
-            @Override
-            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
-                    float velX, float velY, boolean wasFlungOut) {
-                animateDismissing(/* dismissing= */ false);
-                mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY,
-                        wasFlungOut);
-            }
-
-            @Override
-            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                doDismiss();
-            }
-        };
-    }
-
-    /** Updates all the sizes used and applies them to the {@link DismissView}. */
-    public void updateSizes() {
-        Resources res = mContext.getResources();
-        mDismissSize = res.getDimensionPixelSize(
-                R.dimen.floating_task_dismiss_circle_size);
-        final float minDismissSize = res.getDimensionPixelSize(
-                R.dimen.floating_dismiss_circle_small);
-        mDismissSizePercent = minDismissSize / mDismissSize;
-
-        if (mDismissView != null) {
-            mDismissView.updateResources();
-        }
-    }
-
-    /** Prepares the view being dragged to be magnetic. */
-    public void setUpMagneticObject(View viewBeingDragged) {
-        mViewBeingDismissed = viewBeingDragged;
-        mMagnetizedObject = getMagnetizedView(viewBeingDragged);
-        mMagnetizedObject.clearAllTargets();
-        mMagnetizedObject.addTarget(mMagneticTarget);
-        mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener);
-    }
-
-    /** Shows or hides the dismiss target. */
-    public void showDismiss(boolean show) {
-        if (show) {
-            mDismissView.show();
-        } else {
-            mDismissView.hide();
-        }
-    }
-
-    /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
-    public boolean passEventToMagnetizedObject(MotionEvent event) {
-        return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
-    }
-
-    private void createAndAddDismissView() {
-        if (mDismissView != null) {
-            mParent.removeView(mDismissView);
-        }
-        mDismissView = new DismissView(mContext);
-        mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size);
-        mDismissView.updateResources();
-        mParent.addView(mDismissView);
-
-        final float dismissRadius = mDismissSize;
-        // Save the MagneticTarget instance for the newly set up view - we'll add this to the
-        // MagnetizedObjects when the dismiss view gets shown.
-        mMagneticTarget = new MagnetizedObject.MagneticTarget(
-                mDismissView.getCircle(), (int) dismissRadius);
-    }
-
-    private MagnetizedObject<View> getMagnetizedView(View v) {
-        if (mMagnetizedObject != null
-                && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) {
-            // Same view being dragged, we can reuse the magnetic object.
-            return mMagnetizedObject;
-        }
-        MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>(
-                mContext,
-                v,
-                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y
-        ) {
-            @Override
-            public float getWidth(@NonNull View underlyingObject) {
-                return underlyingObject.getWidth();
-            }
-
-            @Override
-            public float getHeight(@NonNull View underlyingObject) {
-                return underlyingObject.getHeight();
-            }
-
-            @Override
-            public void getLocationOnScreen(@NonNull View underlyingObject,
-                    @NonNull int[] loc) {
-                loc[0] = (int) underlyingObject.getTranslationX();
-                loc[1] = (int) underlyingObject.getTranslationY();
-            }
-        };
-        magnetizedView.setHapticsEnabled(true);
-        magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
-        magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY);
-        magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT);
-        return magnetizedView;
-    }
-
-    /** Animates the dismiss treatment on the view being dismissed. */
-    private void animateDismissing(boolean shouldDismiss) {
-        if (mViewBeingDismissed == null) {
-            return;
-        }
-        if (shouldDismiss) {
-            mDismissAnimator.removeAllListeners();
-            mDismissAnimator.start();
-        } else {
-            mDismissAnimator.removeAllListeners();
-            mDismissAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    resetDismissAnimator();
-                }
-            });
-            mDismissAnimator.reverse();
-        }
-    }
-
-    /** Actually dismisses the view. */
-    private void doDismiss() {
-        mDismissView.hide();
-        mController.removeTask();
-        resetDismissAnimator();
-        mViewBeingDismissed = null;
-    }
-
-    private void resetDismissAnimator() {
-        mDismissAnimator.removeAllListeners();
-        mDismissAnimator.cancel();
-        if (mDismissView != null) {
-            mDismissView.cancelAnimators();
-            mDismissView.getCircle().setScaleX(1f);
-            mDismissView.getCircle().setScaleY(1f);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
deleted file mode 100644
index f86d467..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ /dev/null
@@ -1,36 +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.wm.shell.floating;
-
-import android.content.Intent;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to interact with floating tasks.
- */
-@ExternalThread
-public interface FloatingTasks {
-
-    /**
-     * Shows, stashes, or un-stashes the floating task depending on state:
-     * - If there is no floating task for this intent, it shows the task for the provided intent.
-     * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
-     * - If there is a floating task for this intent, and it's not stashed, this stashes it.
-     */
-    void showOrSetStashed(Intent intent);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
deleted file mode 100644
index b3c09d3..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ /dev/null
@@ -1,454 +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.wm.shell.floating;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.os.SystemProperties;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Entry point for creating and managing floating tasks.
- *
- * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView}
- * within that window.
- *
- * Currently optimized for a single task. Multiple tasks are not supported.
- */
-public class FloatingTasksController implements RemoteCallable<FloatingTasksController>,
-        ConfigurationChangeListener {
-
-    private static final String TAG = FloatingTasksController.class.getSimpleName();
-
-    public static final boolean FLOATING_TASKS_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
-    public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES =
-            SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false);
-
-    @VisibleForTesting
-    static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600;
-
-    // Only used for testing
-    private Configuration mConfig;
-    private boolean mFloatingTasksEnabledForTests;
-
-    private FloatingTaskImpl mImpl = new FloatingTaskImpl();
-    private Context mContext;
-    private ShellController mShellController;
-    private ShellCommandHandler mShellCommandHandler;
-    private @Nullable BubbleController mBubbleController;
-    private WindowManager mWindowManager;
-    private ShellTaskOrganizer mTaskOrganizer;
-    private TaskViewTransitions mTaskViewTransitions;
-    private @ShellMainThread ShellExecutor mMainExecutor;
-    // TODO: mBackgroundThread is not used but we'll probs need it eventually?
-    private @ShellBackgroundThread ShellExecutor mBackgroundThread;
-    private SyncTransactionQueue mSyncQueue;
-
-    private boolean mIsFloatingLayerAdded;
-    private FloatingTaskLayer mFloatingTaskLayer;
-    private final Point mLastPosition = new Point(-1, -1);
-
-    private Task mTask;
-
-    // Simple class to hold onto info for intent or shortcut based tasks.
-    public static class Task {
-        public int taskId = INVALID_TASK_ID;
-        @Nullable
-        public Intent intent;
-        @Nullable
-        public ShortcutInfo info;
-        @Nullable
-        public FloatingTaskView floatingView;
-    }
-
-    public FloatingTasksController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            ShellCommandHandler shellCommandHandler,
-            Optional<BubbleController> bubbleController,
-            WindowManager windowManager,
-            ShellTaskOrganizer organizer,
-            TaskViewTransitions transitions,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellBackgroundThread ShellExecutor bgExceutor,
-            SyncTransactionQueue syncTransactionQueue) {
-        mContext = context;
-        mShellController = shellController;
-        mShellCommandHandler = shellCommandHandler;
-        mBubbleController = bubbleController.get();
-        mWindowManager = windowManager;
-        mTaskOrganizer = organizer;
-        mTaskViewTransitions = transitions;
-        mMainExecutor = mainExecutor;
-        mBackgroundThread = bgExceutor;
-        mSyncQueue = syncTransactionQueue;
-        if (isFloatingTasksEnabled()) {
-            shellInit.addInitCallback(this::onInit, this);
-        }
-    }
-
-    protected void onInit() {
-        mShellController.addConfigurationChangeListener(this);
-        mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
-                this::createExternalInterface, this);
-        mShellCommandHandler.addDumpCallback(this::dump, this);
-    }
-
-    /** Only used for testing. */
-    @VisibleForTesting
-    void setConfig(Configuration config) {
-        mConfig = config;
-    }
-
-    /** Only used for testing. */
-    @VisibleForTesting
-    void setFloatingTasksEnabled(boolean enabled) {
-        mFloatingTasksEnabledForTests = enabled;
-    }
-
-    /** Whether the floating layer is available. */
-    boolean isFloatingLayerAvailable() {
-        Configuration config = mConfig == null
-                ? mContext.getResources().getConfiguration()
-                : mConfig;
-        return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-    }
-
-    /** Whether floating tasks are enabled.  */
-    boolean isFloatingTasksEnabled() {
-        return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
-    }
-
-    private ExternalInterfaceBinder createExternalInterface() {
-        return new IFloatingTasksImpl(this);
-    }
-
-    @Override
-    public void onThemeChanged() {
-        if (mIsFloatingLayerAdded) {
-            mFloatingTaskLayer.updateSizes();
-        }
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        // TODO: probably other stuff here to do (e.g. handle rotation)
-        if (mIsFloatingLayerAdded) {
-            mFloatingTaskLayer.updateSizes();
-        }
-    }
-
-    /** Returns false if the task shouldn't be shown. */
-    private boolean canShowTask(Intent intent) {
-        ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask --  %s", intent);
-        if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false;
-        if (intent == null) {
-            ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing");
-            return false;
-        }
-        return true;
-    }
-
-    /** Returns true if the task was or should be shown as a bubble. */
-    private boolean maybeShowTaskAsBubble(Intent intent) {
-        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) {
-            removeFloatingLayer();
-            if (intent.getPackage() != null) {
-                mBubbleController.addAppBubble(intent);
-                ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent);
-            } else {
-                ProtoLog.d(WM_SHELL_FLOATING_APPS,
-                        "failed to show floating task as bubble: %s; unknown package", intent);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Shows, stashes, or un-stashes the floating task depending on state:
-     * - If there is no floating task for this intent, it shows this the provided task.
-     * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
-     * - If there is a floating task for this intent, and it's not stashed, this stashes it.
-     */
-    public void showOrSetStashed(Intent intent) {
-        if (!canShowTask(intent)) return;
-        if (maybeShowTaskAsBubble(intent)) return;
-
-        addFloatingLayer();
-
-        if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
-            // The task is already added, toggle the stash state.
-            mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed());
-            return;
-        }
-
-        // If we're here it's either a new or different task
-        showNewTask(intent);
-    }
-
-    /**
-     * Shows a floating task with the provided intent.
-     * If the same task is present it will un-stash it or do nothing if it is already un-stashed.
-     * Removes any other floating tasks that might exist.
-     */
-    public void showTask(Intent intent) {
-        if (!canShowTask(intent)) return;
-        if (maybeShowTaskAsBubble(intent)) return;
-
-        addFloatingLayer();
-
-        if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
-            // The task is already added, show it if it's stashed.
-            if (mTask.floatingView.isStashed()) {
-                mFloatingTaskLayer.setStashed(mTask, false);
-            }
-            return;
-        }
-        showNewTask(intent);
-    }
-
-    private void showNewTask(Intent intent) {
-        if (mTask != null && !intent.filterEquals(mTask.intent)) {
-            mFloatingTaskLayer.removeAllTaskViews();
-            mTask.floatingView.cleanUpTaskView();
-            mTask = null;
-        }
-
-        FloatingTaskView ftv = new FloatingTaskView(mContext, this);
-        ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
-
-        mTask = new Task();
-        mTask.floatingView = ftv;
-        mTask.intent = intent;
-
-        // Add & start the task.
-        mFloatingTaskLayer.addTask(mTask);
-        ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent);
-        mTask.floatingView.startTask(mMainExecutor, mTask);
-    }
-
-    /**
-     * Removes the task and cleans up the view.
-     */
-    public void removeTask() {
-        if (mTask != null) {
-            ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId);
-
-            if (mTask.floatingView != null) {
-                // TODO: animate it
-                mFloatingTaskLayer.removeView(mTask.floatingView);
-                mTask.floatingView.cleanUpTaskView();
-            }
-            removeFloatingLayer();
-        }
-    }
-
-    /**
-     * Whether there is a floating task and if it is stashed.
-     */
-    public boolean isStashed() {
-        return isTaskAttached(mTask) && mTask.floatingView.isStashed();
-    }
-
-    /**
-     * If a floating task exists, this sets whether it is stashed and animates if needed.
-     */
-    public void setStashed(boolean shouldStash) {
-        if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) {
-            mFloatingTaskLayer.setStashed(mTask, shouldStash);
-        }
-    }
-
-    /**
-     * Saves the last position the floating task was in so that it can be put there again.
-     */
-    public void setLastPosition(int x, int y) {
-        mLastPosition.set(x, y);
-    }
-
-    /**
-     * Returns the last position the floating task was in.
-     */
-    public Point getLastPosition() {
-        return mLastPosition;
-    }
-
-    /**
-     * Whether the provided task has a view that's attached to the floating layer.
-     */
-    private boolean isTaskAttached(Task t) {
-        return t != null && t.floatingView != null
-                && mIsFloatingLayerAdded
-                && mFloatingTaskLayer.getTaskViewCount() > 0
-                && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView);
-    }
-
-    // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this
-    //  type allowed? Bubbles & floating tasks should probably be in the same layer to reduce
-    //  # of windows.
-    private void addFloatingLayer() {
-        if (mIsFloatingLayerAdded) {
-            return;
-        }
-
-        mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager);
-
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
-                PixelFormat.TRANSLUCENT
-        );
-        params.setTrustedOverlay();
-        params.setFitInsetsTypes(0);
-        params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-        params.setTitle("FloatingTaskLayer");
-        params.packageName = mContext.getPackageName();
-        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
-        try {
-            mIsFloatingLayerAdded = true;
-            mWindowManager.addView(mFloatingTaskLayer, params);
-        } catch (IllegalStateException e) {
-            // This means the floating layer has already been added which shouldn't happen.
-            e.printStackTrace();
-        }
-    }
-
-    private void removeFloatingLayer() {
-        if (!mIsFloatingLayerAdded) {
-            return;
-        }
-        try {
-            mIsFloatingLayerAdded = false;
-            if (mFloatingTaskLayer != null) {
-                mWindowManager.removeView(mFloatingTaskLayer);
-            }
-        } catch (IllegalArgumentException e) {
-            // This means the floating layer has already been removed which shouldn't happen.
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Description of current floating task state.
-     */
-    private void dump(PrintWriter pw, String prefix) {
-        pw.println("FloatingTaskController state:");
-        pw.print("   isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable());
-        pw.print("   isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled());
-        pw.print("   mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded);
-        pw.print("   mLastPosition= "); pw.println(mLastPosition);
-        pw.println();
-    }
-
-    /** Returns the {@link FloatingTasks} implementation. */
-    public FloatingTasks asFloatingTasks() {
-        return mImpl;
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public ShellExecutor getRemoteCallExecutor() {
-        return mMainExecutor;
-    }
-
-    /**
-     * The interface for calls from outside the shell, within the host process.
-     */
-    @ExternalThread
-    private class FloatingTaskImpl implements FloatingTasks {
-        @Override
-        public void showOrSetStashed(Intent intent) {
-            mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
-        }
-    }
-
-    /**
-     * The interface for calls from outside the host process.
-     */
-    @BinderThread
-    private static class IFloatingTasksImpl extends IFloatingTasks.Stub
-            implements ExternalInterfaceBinder {
-        private FloatingTasksController mController;
-
-        IFloatingTasksImpl(FloatingTasksController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Invalidates this instance, preventing future calls from updating the controller.
-         */
-        @Override
-        public void invalidate() {
-            mController = null;
-        }
-
-        public void showTask(Intent intent) {
-            executeRemoteCallWithTaskPermission(mController, "showTask",
-                    (controller) ->  controller.showTask(intent));
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
deleted file mode 100644
index c922109..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
+++ /dev/null
@@ -1,73 +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.wm.shell.floating.views;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Displays the menu items for a floating task view (e.g. close).
- */
-public class FloatingMenuView extends LinearLayout {
-
-    private int mItemSize;
-    private int mItemMargin;
-
-    public FloatingMenuView(Context context) {
-        super(context);
-        setOrientation(LinearLayout.HORIZONTAL);
-        setGravity(Gravity.CENTER);
-
-        mItemSize = context.getResources().getDimensionPixelSize(
-                R.dimen.floating_task_menu_item_size);
-        mItemMargin = context.getResources().getDimensionPixelSize(
-                R.dimen.floating_task_menu_item_padding);
-    }
-
-    /** Adds a clickable item to the menu bar. Items are ordered as added. */
-    public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) {
-        ImageView itemView = new ImageView(getContext());
-        itemView.setScaleType(ImageView.ScaleType.CENTER);
-        if (drawable != null) {
-            itemView.setImageDrawable(drawable);
-        }
-        LinearLayout.LayoutParams lp = new LayoutParams(mItemSize,
-                ViewGroup.LayoutParams.MATCH_PARENT);
-        lp.setMarginStart(mItemMargin);
-        lp.setMarginEnd(mItemMargin);
-        addView(itemView, lp);
-
-        itemView.setOnClickListener(listener);
-    }
-
-    /**
-     * The menu extends past the top of the TaskView because of the rounded corners. This means
-     * to center content in the menu we must subtract the radius (i.e. the amount of space covered
-     * by TaskView).
-     */
-    public void setCornerRadius(float radius) {
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
deleted file mode 100644
index 16dab24..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
+++ /dev/null
@@ -1,687 +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.wm.shell.floating.views;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FlingAnimation;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.floating.FloatingDismissController;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and
- * movement of the task views.
- */
-public class FloatingTaskLayer extends FrameLayout
-        implements ViewTreeObserver.OnComputeInternalInsetsListener {
-
-    private static final String TAG = FloatingTaskLayer.class.getSimpleName();
-
-    /** How big to make the task view based on screen width of the largest size. */
-    private static final float START_SIZE_WIDTH_PERCENT = 0.33f;
-    /** Min fling velocity required to move the view from one side of the screen to the other. */
-    private static final float ESCAPE_VELOCITY = 750f;
-    /** Amount of friction to apply to fling animations. */
-    private static final float FLING_FRICTION = 1.9f;
-
-    private final FloatingTasksController mController;
-    private final FloatingDismissController mDismissController;
-    private final WindowManager mWindowManager;
-    private final TouchHandlerImpl mTouchHandler;
-
-    private final Region mTouchableRegion = new Region();
-    private final Rect mPositionRect = new Rect();
-    private final Point mDefaultStartPosition = new Point();
-    private final Point mTaskViewSize = new Point();
-    private WindowInsets mWindowInsets;
-    private int mVerticalPadding;
-    private int mOverhangWhenStashed;
-
-    private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
-    private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener =
-            this::updateSystemGestureExclusion;
-
-    /** Interface allowing something to handle the touch events going to a task. */
-    interface FloatingTaskTouchHandler {
-        void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float viewInitialX, float viewInitialY);
-
-        void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                 float dx, float dy);
-
-        void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float dx, float dy, float velX, float velY);
-
-        void onClick(@NonNull FloatingTaskView v);
-    }
-
-    public FloatingTaskLayer(Context context,
-            FloatingTasksController controller,
-            WindowManager windowManager) {
-        super(context);
-        // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly.
-        setBackgroundColor(Color.argb(0, 0, 0, 0));
-
-        mController = controller;
-        mWindowManager = windowManager;
-        updateSizes();
-
-        // TODO: Might make sense to put dismiss controller in the touch handler since that's the
-        //  main user of dismiss controller.
-        mDismissController = new FloatingDismissController(context, mController, this);
-        mTouchHandler = new TouchHandlerImpl();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-        getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener);
-        setOnApplyWindowInsetsListener((view, windowInsets) -> {
-            if (!windowInsets.equals(mWindowInsets)) {
-                mWindowInsets = windowInsets;
-                updateSizes();
-            }
-            return windowInsets;
-        });
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener);
-    }
-
-    @Override
-    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
-        inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        mTouchableRegion.setEmpty();
-        getTouchableRegion(mTouchableRegion);
-        inoutInfo.touchableRegion.set(mTouchableRegion);
-    }
-
-    /** Adds a floating task to the layout. */
-    public void addTask(FloatingTasksController.Task task) {
-        if (task.floatingView == null) return;
-
-        task.floatingView.setTouchHandler(mTouchHandler);
-        addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y));
-        updateTaskViewPosition(task.floatingView);
-    }
-
-    /** Animates the stashed state of the provided task, if it's part of the floating layer. */
-    public void setStashed(FloatingTasksController.Task task, boolean shouldStash) {
-        if (task.floatingView != null && task.floatingView.getParent() == this) {
-            mTouchHandler.stashTaskView(task.floatingView, shouldStash);
-        }
-    }
-
-    /** Removes all {@link FloatingTaskView} from the layout. */
-    public void removeAllTaskViews() {
-        int childCount = getChildCount();
-        ArrayList<View> viewsToRemove = new ArrayList<>();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i) instanceof FloatingTaskView) {
-                viewsToRemove.add(getChildAt(i));
-            }
-        }
-        for (View v : viewsToRemove) {
-            removeView(v);
-        }
-    }
-
-    /** Returns the number of task views in the layout. */
-    public int getTaskViewCount() {
-        int taskViewCount = 0;
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i) instanceof FloatingTaskView) {
-                taskViewCount++;
-            }
-        }
-        return taskViewCount;
-    }
-
-    /**
-     * Called when the task view is un-stuck from the dismiss target.
-     * @param v the task view being moved.
-     * @param velX the x velocity of the motion event.
-     * @param velY the y velocity of the motion event.
-     * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there
-     *                    was an 'up' event), otherwise the user is still dragging.
-     */
-    public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
-            boolean wasFlungOut) {
-        mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut);
-    }
-
-    /**
-     * Updates dimensions and applies them to any task views.
-     */
-    public void updateSizes() {
-        if (mDismissController != null) {
-            mDismissController.updateSizes();
-        }
-
-        mOverhangWhenStashed = getResources().getDimensionPixelSize(
-                R.dimen.floating_task_stash_offset);
-        mVerticalPadding = getResources().getDimensionPixelSize(
-                R.dimen.floating_task_vertical_padding);
-
-        WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
-        WindowInsets windowInsets = windowMetrics.getWindowInsets();
-        Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
-                | WindowInsets.Type.statusBars()
-                | WindowInsets.Type.displayCutout());
-        Rect bounds = windowMetrics.getBounds();
-        mPositionRect.set(bounds.left + insets.left,
-                bounds.top + insets.top + mVerticalPadding,
-                bounds.right - insets.right,
-                bounds.bottom - insets.bottom - mVerticalPadding);
-
-        int taskViewWidth = Math.max(bounds.height(), bounds.width());
-        int taskViewHeight = Math.min(bounds.height(), bounds.width());
-        taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2));
-        mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight);
-        mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top);
-
-        // Update existing views
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i) instanceof FloatingTaskView) {
-                FloatingTaskView child = (FloatingTaskView) getChildAt(i);
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                lp.width = mTaskViewSize.x;
-                lp.height = mTaskViewSize.y;
-                child.setLayoutParams(lp);
-                updateTaskViewPosition(child);
-            }
-        }
-    }
-
-    /** Returns the first floating task view in the layout. (Currently only ever 1 view). */
-    @Nullable
-    public FloatingTaskView getFirstTaskView() {
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child instanceof FloatingTaskView) {
-                return (FloatingTaskView) child;
-            }
-        }
-        return null;
-    }
-
-    private void updateTaskViewPosition(FloatingTaskView floatingView) {
-        Point lastPosition = mController.getLastPosition();
-        if (lastPosition.x == -1 && lastPosition.y == -1) {
-            floatingView.setX(mDefaultStartPosition.x);
-            floatingView.setY(mDefaultStartPosition.y);
-        } else {
-            floatingView.setX(lastPosition.x);
-            floatingView.setY(lastPosition.y);
-        }
-        if (mTouchHandler.isStashedPosition(floatingView)) {
-            floatingView.setStashed(true);
-        }
-        floatingView.updateLocation();
-    }
-
-    /**
-     * Updates the area of the screen that shouldn't allow the back gesture due to the placement
-     * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would
-     * un-stash the task view instead of performing the back gesture).
-     */
-    private void updateSystemGestureExclusion() {
-        Rect excludeZone = mSystemGestureExclusionRects.get(0);
-        FloatingTaskView floatingTaskView = getFirstTaskView();
-        if (floatingTaskView != null && floatingTaskView.isStashed()) {
-            excludeZone.set(floatingTaskView.getLeft(),
-                    floatingTaskView.getTop(),
-                    floatingTaskView.getRight(),
-                    floatingTaskView.getBottom());
-            excludeZone.offset((int) (floatingTaskView.getTranslationX()),
-                    (int) (floatingTaskView.getTranslationY()));
-            setSystemGestureExclusionRects(mSystemGestureExclusionRects);
-        } else {
-            excludeZone.setEmpty();
-            setSystemGestureExclusionRects(Collections.emptyList());
-        }
-    }
-
-    /**
-     * Fills in the touchable region for floating windows. This is used by WindowManager to
-     * decide which touch events go to the floating windows.
-     */
-    private void getTouchableRegion(Region outRegion) {
-        int childCount = getChildCount();
-        Rect temp = new Rect();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            if (child instanceof FloatingTaskView) {
-                child.getBoundsOnScreen(temp);
-                outRegion.op(temp, Region.Op.UNION);
-            }
-        }
-    }
-
-    /**
-     * Implementation of the touch handler. Animates the task view based on touch events.
-     */
-    private class TouchHandlerImpl implements FloatingTaskTouchHandler {
-        /**
-         * The view can be stashed by swiping it towards the current edge or moving it there. If
-         * the view gets moved in a way that is not one of these gestures, this is flipped to false.
-         */
-        private boolean mCanStash = true;
-        /**
-         * This is used to indicate that the view has been un-stuck from the dismiss target and
-         * needs to spring to the current touch location.
-         */
-        // TODO: implement this behavior
-        private boolean mSpringToTouchOnNextMotionEvent = false;
-
-        private ArrayList<FlingAnimation> mFlingAnimations;
-        private ViewPropertyAnimator mViewPropertyAnimation;
-
-        private float mViewInitialX;
-        private float mViewInitialY;
-
-        private float[] mMinMax = new float[2];
-
-        @Override
-        public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX,
-                float viewInitialY) {
-            mCanStash = true;
-            mViewInitialX = viewInitialX;
-            mViewInitialY = viewInitialY;
-            mDismissController.setUpMagneticObject(v);
-            mDismissController.passEventToMagnetizedObject(ev);
-        }
-
-        @Override
-        public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float dx, float dy) {
-            // Shows the magnetic dismiss target if needed.
-            mDismissController.showDismiss(/* show= */ true);
-
-            // Send it to magnetic target first.
-            if (mDismissController.passEventToMagnetizedObject(ev)) {
-                v.setStashed(false);
-                mCanStash = true;
-
-                return;
-            }
-
-            // If we're here magnetic target didn't want it so move as per normal.
-
-            v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true));
-            v.setTranslationY(capY(v, mViewInitialY + dy));
-            if (v.isStashed()) {
-                // Check if we've moved far enough to be not stashed.
-                final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
-                final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX;
-                if (viewInitiallyOnLeftSide) {
-                    if (v.getTranslationX() > mPositionRect.left) {
-                        v.setStashed(false);
-                        mCanStash = true;
-                    }
-                } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) {
-                    v.setStashed(false);
-                    mCanStash = true;
-                }
-            }
-        }
-
-        // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge.
-        // TODO clean up the code here, pretty hard to comprehend
-        // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges)
-        @Override
-        public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
-                float dx, float dy, float velX, float velY) {
-
-            // Send it to magnetic target first.
-            if (mDismissController.passEventToMagnetizedObject(ev)) {
-                v.setStashed(false);
-                return;
-            }
-            mDismissController.showDismiss(/* show= */ false);
-
-            // If we're here magnetic target didn't want it so handle up as per normal.
-
-            final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false);
-            final float centerX = mPositionRect.centerX();
-            final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX;
-            final boolean viewOnLeftSide = x + v.getWidth() < centerX;
-            final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY;
-            final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY;
-            // TODO: check velX here sometimes it doesn't stash on move when I think it should
-            final boolean shouldStashFromMove =
-                    (velX < 0 && v.getTranslationX() < mPositionRect.left)
-                            || (velX > 0
-                            && v.getTranslationX() + v.getWidth() > mPositionRect.right);
-            final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide
-                    && isFling
-                    && ((viewOnLeftSide && velX < ESCAPE_VELOCITY)
-                    || (!viewOnLeftSide && velX > ESCAPE_VELOCITY));
-            final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove);
-
-            ProtoLog.d(WM_SHELL_FLOATING_APPS,
-                    "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s"
-                    + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f"
-                    + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove,
-                    viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed());
-
-            if (v.isStashed()) {
-                mMinMax[0] = viewOnLeftSide
-                        ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
-                        : mPositionRect.right - v.getWidth();
-                mMinMax[1] = viewOnLeftSide
-                        ? mPositionRect.left
-                        : mPositionRect.right - mOverhangWhenStashed;
-            } else {
-                populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax);
-            }
-
-            boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide;
-            float destinationRelativeX = movingLeft
-                    ? mMinMax[0]
-                    : mMinMax[1];
-
-            // TODO: why is this necessary / when does this happen?
-            if (mMinMax[1] < v.getTranslationX()) {
-                mMinMax[1] = v.getTranslationX();
-            }
-            if (v.getTranslationX() < mMinMax[0]) {
-                mMinMax[0] = v.getTranslationX();
-            }
-
-            // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
-            // so that it'll make it all the way to the side of the screen.
-            final float minimumVelocityToReachEdge =
-                    getMinimumVelocityToReachEdge(v, destinationRelativeX);
-            final float startXVelocity = movingLeft
-                    ? Math.min(minimumVelocityToReachEdge, velX)
-                    : Math.max(minimumVelocityToReachEdge, velX);
-
-            cancelAnyAnimations(v);
-
-            mFlingAnimations = getAnimationForUpEvent(v, shouldStash,
-                    startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX);
-            for (int i = 0; i < mFlingAnimations.size(); i++) {
-                mFlingAnimations.get(i).start();
-            }
-        }
-
-        @Override
-        public void onClick(@NonNull FloatingTaskView v) {
-            if (v.isStashed()) {
-                final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
-                final boolean viewOnLeftSide = v.getTranslationX() < centerX;
-                final float destinationRelativeX = viewOnLeftSide
-                        ? mPositionRect.left
-                        : mPositionRect.right - v.getWidth();
-                final float minimumVelocityToReachEdge =
-                        getMinimumVelocityToReachEdge(v, destinationRelativeX);
-                populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax);
-
-                cancelAnyAnimations(v);
-
-                FlingAnimation flingAnimation = new FlingAnimation(v,
-                        DynamicAnimation.TRANSLATION_X);
-                flingAnimation.setFriction(FLING_FRICTION)
-                        .setStartVelocity(minimumVelocityToReachEdge)
-                        .setMinValue(mMinMax[0])
-                        .setMaxValue(mMinMax[1])
-                        .addEndListener((animation, canceled, value, velocity) -> {
-                            if (canceled) return;
-                            mController.setLastPosition((int) v.getTranslationX(),
-                                    (int) v.getTranslationY());
-                            v.setStashed(false);
-                            v.updateLocation();
-                        });
-                mFlingAnimations = new ArrayList<>();
-                mFlingAnimations.add(flingAnimation);
-                flingAnimation.start();
-            }
-        }
-
-        public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
-                boolean wasFlungOut) {
-            if (wasFlungOut) {
-                snapTaskViewToEdge(v, velX, /* shouldStash= */ false);
-            } else {
-                // TODO: use this for something / to spring the view to the touch location
-                mSpringToTouchOnNextMotionEvent = true;
-            }
-        }
-
-        public void stashTaskView(FloatingTaskView v, boolean shouldStash) {
-            if (v.isStashed() == shouldStash) {
-                return;
-            }
-            final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
-            final boolean viewOnLeftSide = v.getTranslationX() < centerX;
-            snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash);
-        }
-
-        public boolean isStashedPosition(View v) {
-            return v.getTranslationX() < mPositionRect.left
-                    || v.getTranslationX() + v.getWidth() > mPositionRect.right;
-        }
-
-        // TODO: a lot of this is duplicated in onUp -- can it be unified?
-        private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) {
-            final boolean movingLeft = velX < ESCAPE_VELOCITY;
-            populateMinMax(v, movingLeft, shouldStash, mMinMax);
-            float destinationRelativeX = movingLeft
-                    ? mMinMax[0]
-                    : mMinMax[1];
-
-            // TODO: why is this necessary / when does this happen?
-            if (mMinMax[1] < v.getTranslationX()) {
-                mMinMax[1] = v.getTranslationX();
-            }
-            if (v.getTranslationX() < mMinMax[0]) {
-                mMinMax[0] = v.getTranslationX();
-            }
-
-            // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
-            // so that it'll make it all the way to the side of the screen.
-            final float minimumVelocityToReachEdge =
-                    getMinimumVelocityToReachEdge(v, destinationRelativeX);
-            final float startXVelocity = movingLeft
-                    ? Math.min(minimumVelocityToReachEdge, velX)
-                    : Math.max(minimumVelocityToReachEdge, velX);
-
-            cancelAnyAnimations(v);
-
-            mFlingAnimations = getAnimationForUpEvent(v,
-                    shouldStash, startXVelocity,  mMinMax[0], mMinMax[1],
-                    destinationRelativeX);
-            for (int i = 0; i < mFlingAnimations.size(); i++) {
-                mFlingAnimations.get(i).start();
-            }
-        }
-
-        private void cancelAnyAnimations(FloatingTaskView v) {
-            if (mFlingAnimations != null) {
-                for (int i = 0; i < mFlingAnimations.size(); i++) {
-                    if (mFlingAnimations.get(i).isRunning()) {
-                        mFlingAnimations.get(i).cancel();
-                    }
-                }
-            }
-            if (mViewPropertyAnimation != null) {
-                mViewPropertyAnimation.cancel();
-                mViewPropertyAnimation = null;
-            }
-        }
-
-        private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v,
-                boolean shouldStash, float startVelX, float minValue, float maxValue,
-                float destinationRelativeX) {
-            final float ty = v.getTranslationY();
-            final ArrayList<FlingAnimation> animations = new ArrayList<>();
-            if (ty != capY(v, ty)) {
-                // The view was being dismissed so the Y is out of bounds, need to animate that.
-                FlingAnimation yFlingAnimation = new FlingAnimation(v,
-                        DynamicAnimation.TRANSLATION_Y);
-                yFlingAnimation.setFriction(FLING_FRICTION)
-                        .setStartVelocity(startVelX)
-                        .setMinValue(mPositionRect.top)
-                        .setMaxValue(mPositionRect.bottom - mTaskViewSize.y);
-                animations.add(yFlingAnimation);
-            }
-            FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X);
-            flingAnimation.setFriction(FLING_FRICTION)
-                    .setStartVelocity(startVelX)
-                    .setMinValue(minValue)
-                    .setMaxValue(maxValue)
-                    .addEndListener((animation, canceled, value, velocity) -> {
-                        if (canceled) return;
-                        Runnable endAction = () -> {
-                            v.setStashed(shouldStash);
-                            v.updateLocation();
-                            if (!v.isStashed()) {
-                                mController.setLastPosition((int) v.getTranslationX(),
-                                        (int) v.getTranslationY());
-                            }
-                        };
-                        if (!shouldStash) {
-                            final int xTranslation = (int) v.getTranslationX();
-                            if (xTranslation != destinationRelativeX) {
-                                // TODO: this animation doesn't feel great, should figure out
-                                //  a better way to do this or remove the need for it all together.
-                                mViewPropertyAnimation = v.animate()
-                                        .translationX(destinationRelativeX)
-                                        .setListener(getAnimationListener(endAction));
-                                mViewPropertyAnimation.start();
-                            } else {
-                                endAction.run();
-                            }
-                        } else {
-                            endAction.run();
-                        }
-                    });
-            animations.add(flingAnimation);
-            return animations;
-        }
-
-        private AnimatorListenerAdapter getAnimationListener(Runnable endAction) {
-            return new AnimatorListenerAdapter() {
-                boolean translationCanceled = false;
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    super.onAnimationCancel(animation);
-                    translationCanceled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    if (!translationCanceled) {
-                        endAction.run();
-                    }
-                }
-            };
-        }
-
-        private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash,
-                float[] out) {
-            if (shouldStash) {
-                out[0] = onLeft
-                        ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
-                        : mPositionRect.right - v.getWidth();
-                out[1] = onLeft
-                        ? mPositionRect.left
-                        : mPositionRect.right - mOverhangWhenStashed;
-            } else {
-                out[0] = mPositionRect.left;
-                out[1] = mPositionRect.right - mTaskViewSize.x;
-            }
-        }
-
-        private float getMinimumVelocityToReachEdge(FloatingTaskView v,
-                float destinationRelativeX) {
-            // Minimum velocity required for the view to make it to the targeted side of the screen,
-            // taking friction into account (4.2f is the number that friction scalars are multiplied
-            // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the
-            // animation at the end will ensure that it reaches the destination X regardless.
-            return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f);
-        }
-
-        private float capX(FloatingTaskView v, float x, boolean isMoving) {
-            final int width = v.getWidth();
-            if (v.isStashed() || isMoving) {
-                if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) {
-                    return mPositionRect.left - v.getWidth() + mOverhangWhenStashed;
-                }
-                if (x > mPositionRect.right - mOverhangWhenStashed) {
-                    return mPositionRect.right - mOverhangWhenStashed;
-                }
-            } else {
-                if (x < mPositionRect.left) {
-                    return mPositionRect.left;
-                }
-                if (x > mPositionRect.right - width) {
-                    return mPositionRect.right - width;
-                }
-            }
-            return x;
-        }
-
-        private float capY(FloatingTaskView v, float y) {
-            final int height = v.getHeight();
-            if (y < mPositionRect.top) {
-                return mPositionRect.top;
-            }
-            if (y > mPositionRect.bottom - height) {
-                return mPositionRect.bottom - height;
-            }
-            return y;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
deleted file mode 100644
index 581204a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
+++ /dev/null
@@ -1,385 +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.wm.shell.floating.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.RelativeTouchListener;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-/**
- * A view that holds a floating task using {@link TaskView} along with additional UI to manage
- * the task.
- */
-public class FloatingTaskView extends FrameLayout {
-
-    private static final String TAG = FloatingTaskView.class.getSimpleName();
-
-    private FloatingTasksController mController;
-
-    private FloatingMenuView mMenuView;
-    private int mMenuHeight;
-    private TaskView mTaskView;
-
-    private float mCornerRadius = 0f;
-    private int mBackgroundColor;
-
-    private FloatingTasksController.Task mTask;
-
-    private boolean mIsStashed;
-
-    /**
-     * Creates a floating task view.
-     *
-     * @param context the context to use.
-     * @param controller the controller to notify about changes in the floating task (e.g. removal).
-     */
-    public FloatingTaskView(Context context, FloatingTasksController controller) {
-        super(context);
-        mController = controller;
-        setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation));
-        mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size);
-        mMenuView = new FloatingMenuView(context);
-        addView(mMenuView);
-
-        applyThemeAttrs();
-
-        setClipToOutline(true);
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
-            }
-        });
-    }
-
-    // TODO: call this when theme/config changes
-    void applyThemeAttrs() {
-        boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
-                mContext.getResources());
-        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
-                android.R.attr.dialogCornerRadius,
-                android.R.attr.colorBackgroundFloating});
-        mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
-        mCornerRadius = mCornerRadius / 2f;
-        mBackgroundColor = ta.getColor(1, Color.WHITE);
-
-        ta.recycle();
-
-        mMenuView.setCornerRadius(mCornerRadius);
-        mMenuHeight = getResources().getDimensionPixelSize(
-                R.dimen.floating_task_menu_size);
-
-        if (mTaskView != null) {
-            mTaskView.setCornerRadius(mCornerRadius);
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
-        int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
-        measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
-                MeasureSpec.getMode(heightMeasureSpec)));
-
-        if (mTaskView != null) {
-            int taskViewHeight = height - menuViewHeight;
-            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
-                    MeasureSpec.getMode(heightMeasureSpec)));
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        // Drag handle above
-        final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
-        mMenuView.layout(l, t, r, dragHandleBottom);
-        if (mTaskView != null) {
-            // Subtract radius so that the menu extends behind the rounded corners of TaskView.
-            mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
-                    dragHandleBottom + mTaskView.getMeasuredHeight());
-        }
-    }
-
-    /**
-     * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work.
-     */
-    public void createTaskView(Context context, ShellTaskOrganizer organizer,
-            TaskViewTransitions transitions, SyncTransactionQueue syncQueue) {
-        mTaskView = new TaskView(context, organizer, transitions, syncQueue);
-        addView(mTaskView);
-        mTaskView.setEnableSurfaceClipping(true);
-        mTaskView.setCornerRadius(mCornerRadius);
-    }
-
-    /**
-     * Starts the provided task in the TaskView, if the TaskView exists. This should be called after
-     * {@link #createTaskView}.
-     */
-    public void startTask(@ShellMainThread ShellExecutor executor,
-            FloatingTasksController.Task task) {
-        if (mTaskView == null) {
-            Log.e(TAG, "starting task before creating the view!");
-            return;
-        }
-        mTask = task;
-        mTaskView.setListener(executor, mTaskViewListener);
-    }
-
-    /**
-     * Sets the touch handler for the view.
-     *
-     * @param handler the touch handler for the view.
-     */
-    public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) {
-        setOnTouchListener(new RelativeTouchListener() {
-            @Override
-            public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
-                handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY());
-                return true;
-            }
-
-            @Override
-            public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
-                    float viewInitialY, float dx, float dy) {
-                handler.onMove(FloatingTaskView.this, ev, dx, dy);
-            }
-
-            @Override
-            public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
-                    float viewInitialY, float dx, float dy, float velX, float velY) {
-                handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY);
-            }
-        });
-        setOnClickListener(view -> {
-            handler.onClick(FloatingTaskView.this);
-        });
-
-        mMenuView.addMenuItem(null, view -> {
-            if (mIsStashed) {
-                // If we're stashed all clicks un-stash.
-                handler.onClick(FloatingTaskView.this);
-            }
-        });
-    }
-
-    private void setContentVisibility(boolean visible) {
-        if (mTaskView == null) return;
-        mTaskView.setAlpha(visible ? 1f : 0f);
-    }
-
-    /**
-     * Sets the alpha of both this view and the TaskView.
-     */
-    public void setTaskViewAlpha(float alpha) {
-        if (mTaskView != null) {
-            mTaskView.setAlpha(alpha);
-        }
-        setAlpha(alpha);
-    }
-
-    /**
-     * Call when the location or size of the view has changed to update TaskView.
-     */
-    public void updateLocation() {
-        if (mTaskView == null) return;
-        mTaskView.onLocationChanged();
-    }
-
-    private void updateMenuColor() {
-        ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
-        int color = info != null ? info.taskDescription.getBackgroundColor() : -1;
-        if (color != -1) {
-            mMenuView.setBackgroundColor(color);
-        } else {
-            mMenuView.setBackgroundColor(mBackgroundColor);
-        }
-    }
-
-    /**
-     * Sets whether the view is stashed or not.
-     *
-     * Also updates the touchable area based on this. If the view is stashed we don't direct taps
-     * on the activity to the activity, instead a tap will un-stash the view.
-     */
-    public void setStashed(boolean isStashed) {
-        if (mIsStashed != isStashed) {
-            mIsStashed = isStashed;
-            if (mTaskView == null) {
-                return;
-            }
-            updateObscuredTouchRect();
-        }
-    }
-
-    /** Whether the view is stashed at the edge of the screen or not. **/
-    public boolean isStashed() {
-        return mIsStashed;
-    }
-
-    private void updateObscuredTouchRect() {
-        if (mIsStashed) {
-            Rect tmpRect = new Rect();
-            getBoundsOnScreen(tmpRect);
-            mTaskView.setObscuredTouchRect(tmpRect);
-        } else {
-            mTaskView.setObscuredTouchRect(null);
-        }
-    }
-
-    /**
-     * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has
-     * been called on this view or if
-     * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called.
-     */
-    public boolean needsTaskStarted() {
-        // If the task needs to be restarted then TaskView would have been cleaned up.
-        return mTaskView == null;
-    }
-
-    /** Call this when the floating task activity is no longer in use. */
-    public void cleanUpTaskView() {
-        if (mTask != null && mTask.taskId != INVALID_TASK_ID) {
-            try {
-                ActivityTaskManager.getService().removeTask(mTask.taskId);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.getMessage());
-            }
-        }
-        if (mTaskView != null) {
-            mTaskView.release();
-            removeView(mTaskView);
-            mTaskView = null;
-        }
-    }
-
-    // TODO: use task background colour / how to get the taskInfo ?
-    private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) {
-        final int taskBgColor = taskInfo.taskDescription.getStatusBarColor();
-        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
-    }
-
-    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
-        private boolean mInitialized = false;
-        private boolean mDestroyed = false;
-
-        @Override
-        public void onInitialized() {
-            if (mDestroyed || mInitialized) {
-                return;
-            }
-            // Custom options so there is no activity transition animation
-            ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                    /* enterResId= */ 0, /* exitResId= */ 0);
-
-            Rect launchBounds = new Rect();
-            mTaskView.getBoundsOnScreen(launchBounds);
-
-            try {
-                options.setTaskAlwaysOnTop(true);
-                if (mTask.intent != null) {
-                    Intent fillInIntent = new Intent();
-                    // Apply flags to make behaviour match documentLaunchMode=always.
-                    fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
-                    fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-
-                    PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent,
-                            PendingIntent.FLAG_MUTABLE,
-                            null);
-                    mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
-                } else {
-                    ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent");
-                }
-            } catch (RuntimeException e) {
-                ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s",
-                        e.getMessage());
-                mController.removeTask();
-            }
-            mInitialized = true;
-        }
-
-        @Override
-        public void onReleased() {
-            mDestroyed = true;
-        }
-
-        @Override
-        public void onTaskCreated(int taskId, ComponentName name) {
-            mTask.taskId = taskId;
-            updateMenuColor();
-            setContentVisibility(true);
-        }
-
-        @Override
-        public void onTaskVisibilityChanged(int taskId, boolean visible) {
-            setContentVisibility(visible);
-        }
-
-        @Override
-        public void onTaskRemovalStarted(int taskId) {
-            // Must post because this is called from a binder thread.
-            post(() -> {
-                mController.removeTask();
-                cleanUpTaskView();
-            });
-        }
-
-        @Override
-        public void onBackPressedOnTaskRoot(int taskId) {
-            if (mTask.taskId == taskId && !mIsStashed) {
-                // TODO: is removing the window the desired behavior?
-                post(() -> mController.removeTask());
-            }
-        }
-    };
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index f4888fb..168f6d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -98,9 +98,11 @@
 
             switch (change.getMode()) {
                 case WindowManager.TRANSIT_OPEN:
-                case WindowManager.TRANSIT_TO_FRONT:
                     onOpenTransitionReady(change, startT, finishT);
                     break;
+                case WindowManager.TRANSIT_TO_FRONT:
+                    onToFrontTransitionReady(change, startT, finishT);
+                    break;
                 case WindowManager.TRANSIT_CLOSE: {
                     taskInfoList.add(change.getTaskInfo());
                     onCloseTransitionReady(change, startT, finishT);
@@ -138,6 +140,21 @@
                 change.getTaskInfo(), startT, finishT);
     }
 
+    private void onToFrontTransitionReady(
+            TransitionInfo.Change change,
+            SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT) {
+        boolean exists = mWindowDecorViewModel.setupWindowDecorationForTransition(
+                change.getTaskInfo(),
+                startT,
+                finishT);
+        if (!exists) {
+            // Window caption does not exist, create it
+            mWindowDecorViewModel.createWindowDecoration(
+                    change.getTaskInfo(), change.getLeash(), startT, finishT);
+        }
+    }
+
     @Override
     public void onTransitionStarting(@NonNull IBinder transition) {}
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index f707363..0006244 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,7 +23,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.RemoteAction;
@@ -74,11 +73,6 @@
     void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
 
     /**
-     * Wait until the next frame to run the given Runnable.
-     */
-    void runWithNextFrame(@NonNull Runnable runnable);
-
-    /**
      * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
      * need to synchronize the movements on the same frame as PiP.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2d7c5ce..f170e77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,10 +179,8 @@
                 // This is necessary in case there was a resize animation ongoing when exit PIP
                 // started, in which case the first resize will be skipped to let the exit
                 // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResizeDelayedIfNeeded(() -> {
-                    finishResize(tx, destinationBounds, direction, animationType);
-                    sendOnPipTransitionFinished(direction);
-                });
+                finishResize(tx, destinationBounds, direction, animationType);
+                sendOnPipTransitionFinished(direction);
             }
         }
 
@@ -198,34 +196,6 @@
         }
     };
 
-    /**
-     * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
-     *
-     * This is done to avoid a race condition between the last transaction applied in
-     * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
-     * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
-     * gets applied before the transaction created by the last onAnimationUpdate. As a result of
-     * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
-     * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
-     * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
-     * WCT application.
-     *
-     * The race only happens when the PiP surface transaction has to be synced with the PiP menu
-     * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
-     * the PiP menu contents.
-     */
-    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
-        if (!shouldSyncPipTransactionWithMenu()) {
-            finishResizeRunnable.run();
-            return;
-        }
-        mPipMenuController.runWithNextFrame(finishResizeRunnable);
-    }
-
-    private boolean shouldSyncPipTransactionWithMenu() {
-        return mPipMenuController.isMenuVisible();
-    }
-
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -251,7 +221,7 @@
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, Rect destinationBounds) {
-                    if (shouldSyncPipTransactionWithMenu()) {
+                    if (mPipMenuController.isMenuVisible()) {
                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
                         return true;
                     }
@@ -1253,7 +1223,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
-        if (shouldSyncPipTransactionWithMenu()) {
+        if (mPipMenuController.isMenuVisible()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
@@ -1295,7 +1265,7 @@
         mSurfaceTransactionHelper
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
-        if (shouldSyncPipTransactionWithMenu()) {
+        if (mPipMenuController.isMenuVisible()) {
             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 531b9de..a0a8f9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,18 +305,6 @@
                 showResizeHandle);
     }
 
-    @Override
-    public void runWithNextFrame(Runnable runnable) {
-        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
-            runnable.run();
-        }
-
-        mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
-            mMainHandler.post(runnable);
-        });
-        mPipMenuView.invalidate();
-    }
-
     /**
      * Move the PiP menu, which does a translation and possibly a scale transformation.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index c39400a..ab7edbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -421,18 +421,6 @@
     }
 
     @Override
-    public void runWithNextFrame(Runnable runnable) {
-        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
-            runnable.run();
-        }
-
-        mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
-            mMainHandler.post(runnable);
-        });
-        mPipMenuView.invalidate();
-    }
-
-    @Override
     public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
             Rect pipDestBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 89205a6..519ec14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -175,6 +175,9 @@
     }
 
     private void onInit() {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mOrganizer.shareTransactionQueue();
+        }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
                 this::createExternalInterface, this);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 36dd8ed..ca15f00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -105,6 +105,11 @@
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
         if (!shouldShowWindowDecor(taskInfo)) return false;
+        CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+        if (oldDecoration != null) {
+            // close the old decoration if it exists to avoid two window decorations being added
+            oldDecoration.close();
+        }
         final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
                 mContext,
                 mDisplayController,
@@ -141,23 +146,25 @@
     }
 
     @Override
-    public void setupWindowDecorationForTransition(
+    public boolean setupWindowDecorationForTransition(
             RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
         final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
-        if (decoration == null) return;
+        if (decoration == null) return false;
 
         decoration.relayout(taskInfo, startT, finishT);
+        return true;
     }
 
     @Override
-    public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+    public boolean destroyWindowDecoration(RunningTaskInfo taskInfo) {
         final CaptionWindowDecoration decoration =
                 mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
-        if (decoration == null) return;
+        if (decoration == null) return false;
 
         decoration.close();
+        return true;
     }
 
     private class CaptionTouchEventListener implements
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d7f71c8..2ce4d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -44,7 +44,7 @@
      * @param taskSurface the surface of the task
      * @param startT the start transaction to be applied before the transition
      * @param finishT the finish transaction to restore states after the transition
-     * @return the window decoration object
+     * @return {@code true} if window decoration was created, {@code false} otherwise
      */
     boolean createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
@@ -66,8 +66,9 @@
      *
      * @param startT the start transaction to be applied before the transition
      * @param finishT the finish transaction to restore states after the transition
+     * @return {@code true} if window decoration exists, {@code false} otherwise
      */
-    void setupWindowDecorationForTransition(
+    boolean setupWindowDecorationForTransition(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT);
@@ -76,6 +77,7 @@
      * Destroys the window decoration of the give task.
      *
      * @param taskInfo the info of the task
+     * @return {@code true} if window decoration was destroyed, {@code false} otherwise
      */
-    void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+    boolean destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 6f1ff99..8765ad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -45,14 +45,23 @@
 fun FlickerTestParameter.splitScreenEntered(
     component1: IComponentMatcher,
     component2: IComponentMatcher,
-    fromOtherApp: Boolean
+    fromOtherApp: Boolean,
+    appExistAtStart: Boolean = true
 ) {
     if (fromOtherApp) {
-        appWindowIsInvisibleAtStart(component1)
+        if (appExistAtStart) {
+            appWindowIsInvisibleAtStart(component1)
+        } else {
+            appWindowIsNotContainAtStart(component1)
+        }
     } else {
         appWindowIsVisibleAtStart(component1)
     }
-    appWindowIsInvisibleAtStart(component2)
+    if (appExistAtStart) {
+        appWindowIsInvisibleAtStart(component2)
+    } else {
+        appWindowIsNotContainAtStart(component2)
+    }
     splitScreenDividerIsInvisibleAtStart()
 
     appWindowIsVisibleAtEnd(component1)
@@ -315,6 +324,10 @@
     assertWmEnd { this.isAppWindowInvisible(component) }
 }
 
+fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+    assertWmStart { this.notContains(component) }
+}
+
 fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component) }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 1a29193..e9c765a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -23,7 +23,6 @@
 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.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -78,7 +77,8 @@
 
     @Postsubmit
     @Test
-    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
+        fromOtherApp = false, appExistAtStart = false)
 
     @Postsubmit
     @Test
@@ -108,7 +108,15 @@
 
     @Postsubmit
     @Test
-    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+    fun secondaryAppWindowBecomesVisible() {
+        testSpec.assertWm {
+            this.notContains(secondaryApp)
+                .then()
+                .isAppWindowInvisible(secondaryApp, isOptional = true)
+                .then()
+                .isAppWindowVisible(secondaryApp)
+        }
+    }
 
     /** {@inheritDoc} */
     @Postsubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index f6d6c03..5332476 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -140,7 +140,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
 
-        mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
     }
@@ -152,7 +152,7 @@
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
                 DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
 
-        mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
+        mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
         waitDividerFlingFinished();
         verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
deleted file mode 100644
index d378a17..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ /dev/null
@@ -1,261 +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.wm.shell.floating;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-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.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
-
-import org.junit.After;
-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;
-
-import java.util.Optional;
-
-/**
- * Tests for the floating tasks controller.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FloatingTasksControllerTest extends ShellTestCase {
-    // Some behavior in the controller constructor is dependent on this so we can only
-    // validate if it's working for the real value for those things.
-    private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
-
-    @Mock private ShellInit mShellInit;
-    @Mock private ShellController mShellController;
-    @Mock private WindowManager mWindowManager;
-    @Mock private ShellTaskOrganizer mTaskOrganizer;
-    @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor;
-
-    private FloatingTasksController mController;
-
-    @Before
-    public void setUp() throws RemoteException {
-        MockitoAnnotations.initMocks(this);
-
-        WindowMetrics windowMetrics = mock(WindowMetrics.class);
-        WindowInsets windowInsets = mock(WindowInsets.class);
-        Insets insets = Insets.of(0, 0, 0, 0);
-        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
-        when(windowMetrics.getWindowInsets()).thenReturn(windowInsets);
-        when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000));
-        when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets);
-
-        // For the purposes of this test, just run everything synchronously
-        ShellExecutor shellExecutor = new TestShellExecutor();
-        when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor);
-    }
-
-    @After
-    public void tearDown() {
-        if (mController != null) {
-            mController.removeTask();
-            mController = null;
-        }
-    }
-
-    private void setUpTabletConfig() {
-        Configuration config = mock(Configuration.class);
-        config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-        mController.setConfig(config);
-    }
-
-    private void setUpPhoneConfig() {
-        Configuration config = mock(Configuration.class);
-        config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1;
-        mController.setConfig(config);
-    }
-
-    private void createController() {
-        mController = new FloatingTasksController(mContext,
-                mShellInit,
-                mShellController,
-                mock(ShellCommandHandler.class),
-                Optional.empty(),
-                mWindowManager,
-                mTaskOrganizer,
-                mock(TaskViewTransitions.class),
-                mock(ShellExecutor.class),
-                mock(ShellExecutor.class),
-                mock(SyncTransactionQueue.class));
-        spyOn(mController);
-    }
-
-    //
-    // Shell specific
-    //
-    @Test
-    public void instantiateController_addInitCallback() {
-        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-            setUpTabletConfig();
-
-            verify(mShellInit, times(1)).addInitCallback(any(), any());
-        }
-    }
-
-    @Test
-    public void instantiateController_doesntAddInitCallback() {
-        if (!FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-
-            verify(mShellInit, never()).addInitCallback(any(), any());
-        }
-    }
-
-    @Test
-    public void onInit_registerConfigChangeListener() {
-        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-            setUpTabletConfig();
-            mController.onInit();
-
-            verify(mShellController, times(1)).addConfigurationChangeListener(any());
-        }
-    }
-
-    @Test
-    public void onInit_addExternalInterface() {
-        if (FLOATING_TASKS_ACTUALLY_ENABLED) {
-            createController();
-            setUpTabletConfig();
-            mController.onInit();
-
-            verify(mShellController, times(1)).addExternalInterface(
-                    ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
-        }
-    }
-
-    //
-    // Tests for floating layer, which is only available for tablets.
-    //
-
-    @Test
-    public void testIsFloatingLayerAvailable_true() {
-        createController();
-        setUpTabletConfig();
-        assertThat(mController.isFloatingLayerAvailable()).isTrue();
-    }
-
-    @Test
-    public void testIsFloatingLayerAvailable_false() {
-        createController();
-        setUpPhoneConfig();
-        assertThat(mController.isFloatingLayerAvailable()).isFalse();
-    }
-
-    //
-    // Tests for floating tasks being enabled, guarded by sysprop flag.
-    //
-
-    @Test
-    public void testIsFloatingTasksEnabled_true() {
-        createController();
-        mController.setFloatingTasksEnabled(true);
-        setUpTabletConfig();
-        assertThat(mController.isFloatingTasksEnabled()).isTrue();
-    }
-
-    @Test
-    public void testIsFloatingTasksEnabled_false() {
-        createController();
-        mController.setFloatingTasksEnabled(false);
-        setUpTabletConfig();
-        assertThat(mController.isFloatingTasksEnabled()).isFalse();
-    }
-
-    //
-    // Tests for behavior depending on flags
-    //
-
-    @Test
-    public void testShowTaskIntent_enabled() {
-        createController();
-        mController.setFloatingTasksEnabled(true);
-        setUpTabletConfig();
-
-        mController.showTask(mock(Intent.class));
-        verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
-        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
-    }
-
-    @Test
-    public void testShowTaskIntent_notEnabled() {
-        createController();
-        mController.setFloatingTasksEnabled(false);
-        setUpTabletConfig();
-
-        mController.showTask(mock(Intent.class));
-        verify(mWindowManager, never()).addView(any(), any());
-    }
-
-    @Test
-    public void testRemoveTask() {
-        createController();
-        mController.setFloatingTasksEnabled(true);
-        setUpTabletConfig();
-
-        mController.showTask(mock(Intent.class));
-        verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
-        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
-
-        mController.removeTask();
-        verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture());
-        assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0);
-    }
-}
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index 15be80c..d1cfd03 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <memory>
 #include <utility>
 
 #include "androidfw/CursorWindow.h"
@@ -184,7 +185,7 @@
     ASSERT_EQ(w->allocRow(), OK);
 
     // Scratch buffer that will fit before inflation
-    void* buf = malloc(kHalfInlineSize);
+    char buf[kHalfInlineSize];
 
     // Store simple value
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -262,7 +263,7 @@
     ASSERT_EQ(w->allocRow(), OK);
 
     // Scratch buffer that will fit before inflation
-    void* buf = malloc(kHalfInlineSize);
+    char buf[kHalfInlineSize];
 
     // Store simple value
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -322,7 +323,8 @@
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
 
     // Store object that forces inflation
-    void* buf = malloc(kGiantSize);
+    std::unique_ptr<char> bufPtr(new char[kGiantSize]);
+    void* buf = bufPtr.get();
     memset(buf, 42, kGiantSize);
     ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29f3773..cccc0f81 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -678,6 +678,7 @@
     srcs: [
         "tests/unit/main.cpp",
         "tests/unit/ABitmapTests.cpp",
+        "tests/unit/AutoBackendTextureReleaseTests.cpp",
         "tests/unit/CacheManagerTests.cpp",
         "tests/unit/CanvasContextTests.cpp",
         "tests/unit/CanvasOpTests.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index ef5eacb..b656b6a 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -32,9 +32,17 @@
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
     GrBackendFormat backendFormat =
             GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+    LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
+                        __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
+                                 ", AHardwareBuffer_Format==%" PRIu32 ".",
+                        static_cast<int>(context->backend()), desc.format);
     mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
             context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
             createProtectedImage, backendFormat, false);
+    LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
+                        __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
+                                 ", protected==%d",
+                        desc.width, desc.height, createProtectedImage);
 }
 
 void AutoBackendTextureRelease::unref(bool releaseImage) {
@@ -74,13 +82,13 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+    // The following ref will be counteracted by Skia calling releaseProc, either during
+    // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
+    // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+    ref();
     mImage = SkImage::MakeFromTexture(
             context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
             uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
-    if (mImage.get()) {
-        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
-        ref();
-    }
 }
 
 void AutoBackendTextureRelease::newBufferContent(GrDirectContext* context) {
diff --git a/libs/hwui/AutoBackendTextureRelease.h b/libs/hwui/AutoBackendTextureRelease.h
index c9bb767..f0eb2a8 100644
--- a/libs/hwui/AutoBackendTextureRelease.h
+++ b/libs/hwui/AutoBackendTextureRelease.h
@@ -25,6 +25,9 @@
 namespace android {
 namespace uirenderer {
 
+// Friend TestUtils serves as a proxy for any test cases that require access to private members.
+class TestUtils;
+
 /**
  * AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object
  * that keeps GPU resources alive until the last SkImage object using them is destroyed.
@@ -66,6 +69,9 @@
 
     // mImage is the SkImage created from mBackendTexture.
     sk_sp<SkImage> mImage;
+
+    // Friend TestUtils serves as a proxy for any test cases that require access to private members.
+    friend class TestUtils;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 75865c7..9d5c13e 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <AutoBackendTextureRelease.h>
 #include <DisplayList.h>
 #include <Matrix.h>
 #include <Properties.h>
@@ -293,6 +294,11 @@
     static SkRect getClipBounds(const SkCanvas* canvas);
     static SkRect getLocalClipBounds(const SkCanvas* canvas);
 
+    static int getUsageCount(const AutoBackendTextureRelease* textureRelease) {
+        EXPECT_NE(nullptr, textureRelease);
+        return textureRelease->mUsageCount;
+    }
+
     struct CallCounts {
         int sync = 0;
         int contextDestroyed = 0;
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
new file mode 100644
index 0000000..2ec78a4
--- /dev/null
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "AutoBackendTextureRelease.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+AHardwareBuffer* allocHardwareBuffer() {
+    AHardwareBuffer* buffer;
+    AHardwareBuffer_Desc desc = {
+            .width = 16,
+            .height = 16,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+    };
+    constexpr int kSucceeded = 0;
+    int status = AHardwareBuffer_allocate(&desc, &buffer);
+    EXPECT_EQ(kSucceeded, status);
+    return buffer;
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_invalid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) {
+    AHardwareBuffer* buffer = allocHardwareBuffer();
+    AutoBackendTextureRelease* textureRelease =
+            new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+    textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->unref(true);
+    AHardwareBuffer_release(buffer);
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_valid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_valid) {
+    AHardwareBuffer* buffer = allocHardwareBuffer();
+    AutoBackendTextureRelease* textureRelease =
+            new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, renderThread.getGrContext());
+
+    EXPECT_EQ(2, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->unref(true);
+    AHardwareBuffer_release(buffer);
+}
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
index 8c40338..8e1bb1f0 100644
--- a/location/java/android/location/Country.java
+++ b/location/java/android/location/Country.java
@@ -16,6 +16,9 @@
 
 package android.location;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,7 +31,8 @@
  *
  * @hide
  */
-public class Country implements Parcelable {
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+public final class Country implements Parcelable {
     /**
      * The country code came from the mobile network
      */
@@ -78,6 +82,8 @@
      *        <li>{@link #COUNTRY_SOURCE_SIM}</li>
      *        <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
      *        </ul>
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public Country(final String countryIso, final int source) {
@@ -100,6 +106,7 @@
         mTimestamp = timestamp;
     }
 
+    /** @hide */
     public Country(Country country) {
         mCountryIso = country.mCountryIso;
         mSource = country.mSource;
@@ -109,8 +116,8 @@
     /**
      * @return the ISO 3166-1 two letters country code
      */
-    @UnsupportedAppUsage
-    public final String getCountryIso() {
+    @NonNull
+    public String getCountryIso() {
         return mCountryIso;
     }
 
@@ -124,20 +131,22 @@
      *         <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
      *         </ul>
      */
-    @UnsupportedAppUsage
-    public final int getSource() {
+    public int getSource() {
         return mSource;
     }
 
     /**
      * Returns the time that this object was created (which we assume to be the time that the source
      * was consulted).
+     *
+     * @hide
      */
-    public final long getTimestamp() {
+    public long getTimestamp() {
         return mTimestamp;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {
+    @android.annotation.NonNull
+    public static final Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {
         public Country createFromParcel(Parcel in) {
             return new Country(in.readString(), in.readInt(), in.readLong());
         }
@@ -147,11 +156,13 @@
         }
     };
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
-    public void writeToParcel(Parcel parcel, int flags) {
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeString(mCountryIso);
         parcel.writeInt(mSource);
         parcel.writeLong(mTimestamp);
@@ -161,9 +172,10 @@
      * Returns true if this {@link Country} is equivalent to the given object. This ignores
      * the timestamp value and just checks for equivalence of countryIso and source values.
      * Returns false otherwise.
+     *
      */
     @Override
-    public boolean equals(Object object) {
+    public boolean equals(@Nullable Object object) {
         if (object == this) {
             return true;
         }
@@ -194,12 +206,15 @@
      * @param country the country to compare
      * @return true if the specified country's countryIso field is equal to this
      *         country's, false otherwise.
+     *
+     * @hide
      */
     public boolean equalsIgnoreSource(Country country) {
         return country != null && mCountryIso.equals(country.getCountryIso());
     }
 
     @Override
+    @NonNull
     public String toString() {
         return "Country {ISO=" + mCountryIso + ", source=" + mSource + ", time=" + mTimestamp + "}";
     }
diff --git a/location/java/android/location/CountryDetector.java b/location/java/android/location/CountryDetector.java
index e344b82..3a0edfc 100644
--- a/location/java/android/location/CountryDetector.java
+++ b/location/java/android/location/CountryDetector.java
@@ -16,6 +16,9 @@
 
 package android.location;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -47,6 +50,7 @@
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
 @SystemService(Context.COUNTRY_DETECTOR)
 public class CountryDetector {
 
@@ -101,7 +105,7 @@
      * @return the country if it is available immediately, otherwise null will
      *         be returned.
      */
-    @UnsupportedAppUsage
+    @Nullable
     public Country detectCountry() {
         try {
             return mService.detectCountry();
@@ -120,8 +124,7 @@
      *        implement the callback mechanism. If looper is null then the
      *        callbacks will be called on the main thread.
      */
-    @UnsupportedAppUsage
-    public void addCountryListener(CountryListener listener, Looper looper) {
+    public void addCountryListener(@NonNull CountryListener listener, @Nullable Looper looper) {
         synchronized (mListeners) {
             if (!mListeners.containsKey(listener)) {
                 ListenerTransport transport = new ListenerTransport(listener, looper);
@@ -138,8 +141,7 @@
     /**
      * Remove the listener
      */
-    @UnsupportedAppUsage
-    public void removeCountryListener(CountryListener listener) {
+    public void removeCountryListener(@NonNull CountryListener listener) {
         synchronized (mListeners) {
             ListenerTransport transport = mListeners.get(listener);
             if (transport != null) {
diff --git a/location/java/android/location/CountryListener.java b/location/java/android/location/CountryListener.java
index eb67205..5c06d82 100644
--- a/location/java/android/location/CountryListener.java
+++ b/location/java/android/location/CountryListener.java
@@ -16,7 +16,8 @@
 
 package android.location;
 
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 
 /**
  * The listener for receiving the notification when the country is detected or
@@ -24,10 +25,11 @@
  *
  * @hide
  */
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
 public interface CountryListener {
     /**
      * @param country the changed or detected country.
+     *
      */
-    @UnsupportedAppUsage
-    void onCountryDetected(Country country);
+    void onCountryDetected(@NonNull Country country);
 }
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 98578b2..4f45602 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -138,6 +138,7 @@
             @SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags,
             @SubHalPowerCapabilityFlags int powerFlags,
             @NonNull List<GnssSignalType> gnssSignalTypes) {
+        Objects.requireNonNull(gnssSignalTypes);
         mTopFlags = topFlags;
         mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
         mPowerFlags = powerFlags;
@@ -190,6 +191,21 @@
     }
 
     /**
+     * Returns a new GnssCapabilities object with a list of GnssSignalType.
+     *
+     * @hide
+     */
+    public GnssCapabilities withSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) {
+        Objects.requireNonNull(gnssSignalTypes);
+        if (mGnssSignalTypes.equals(gnssSignalTypes)) {
+            return this;
+        } else {
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    new ArrayList<>(gnssSignalTypes));
+        }
+    }
+
+    /**
      * Returns {@code true} if GNSS chipset supports scheduling, {@code false} otherwise.
      */
     public boolean hasScheduling() {
@@ -452,7 +468,8 @@
         GnssCapabilities that = (GnssCapabilities) o;
         return mTopFlags == that.mTopFlags
                 && mMeasurementCorrectionsFlags == that.mMeasurementCorrectionsFlags
-                && mPowerFlags == that.mPowerFlags;
+                && mPowerFlags == that.mPowerFlags
+                && mGnssSignalTypes.equals(that.mGnssSignalTypes);
     }
 
     @Override
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
index f9c6e72..16c3f2e 100644
--- a/location/java/android/location/GnssSignalType.java
+++ b/location/java/android/location/GnssSignalType.java
@@ -116,7 +116,7 @@
     public String toString() {
         StringBuilder s = new StringBuilder();
         s.append("GnssSignalType[");
-        s.append(" Constellation=").append(mConstellationType);
+        s.append("Constellation=").append(mConstellationType);
         s.append(", CarrierFrequencyHz=").append(mCarrierFrequencyHz);
         s.append(", CodeType=").append(mCodeType);
         s.append(']');
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index a27fd10..7d84fb2 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1047,7 +1047,7 @@
             }
         }
 
-        void notifyRecordingStarted(@Nullable String recordingId) {
+        void notifyRecordingStarted(String recordingId) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 3d65effa..2956a0a 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -456,9 +456,10 @@
 
         /**
          * Receives started recording's ID.
-         * @hide
+         *
+         * @param recordingId The ID of the recording started
          */
-        public void onRecordingStarted(@Nullable String recordingId) {
+        public void onRecordingStarted(@NonNull String recordingId) {
         }
 
         /**
@@ -914,7 +915,15 @@
         /**
          * Requests starting of recording
          *
-         * @hide
+         * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+         * call {@link android.media.tv.TvRecordingClient#startRecording(Uri)} with the provided
+         * {@code programUri}.
+         * A non-null {@code programUri} implies the started recording should be of that specific
+         * program, whereas null {@code programUri} does not impose such a requirement and the
+         * recording can span across multiple TV programs.
+         *
+         * @param programUri The URI for the TV program to record.
+         * @see android.media.tv.TvRecordingClient#startRecording(Uri)
          */
         @CallSuper
         public void requestStartRecording(@Nullable Uri programUri) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 76ba69c..1f270d0 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -583,11 +583,9 @@
     /**
      * Alerts the TV interactive app that a recording has been started with recordingId
      *
-     * @param recordingId The Id of the recording started
-     *
-     * @hide
+     * @param recordingId The ID of the recording started
      */
-    public void notifyRecordingStarted(@Nullable String recordingId) {
+    public void notifyRecordingStarted(@NonNull String recordingId) {
         if (DEBUG) {
             Log.d(TAG, "notifyRecordingStarted");
         }
@@ -859,10 +857,9 @@
         /**
          * This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)}
          * is called.
+         *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param programUri The program URI to record
-         *
-         * @hide
          */
         public void onRequestStartRecording(
                 @NonNull String iAppServiceId,
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
index edb5e37..158e698 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
@@ -158,7 +158,7 @@
         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
         filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
         filter.addAction(ACTION_USB_PERMISSION);
-        context.registerReceiver(mUsbReceiver, filter);
+        context.registerReceiver(mUsbReceiver, filter, Context.RECEIVER_EXPORTED/*UNAUDITED*/);
     }
 
     /**
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 1709dfd..10c570b 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -93,7 +93,7 @@
     ],
     static_libs: ["libarect"],
     fuzz_config: {
-        cc: ["scroggo@google.com"],
+        cc: ["dichenzhang@google.com","scroggo@google.com"],
         asan_options: [
             "detect_odr_violation=1",
         ],
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 62ffe38..a216381 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -10,7 +10,7 @@
 android_app {
     name: "CarrierDefaultApp",
     srcs: ["src/**/*.java"],
-    libs: ["SliceStore"],
+    libs: ["SlicePurchaseController"],
     platform_apis: true,
     certificate: "platform",
     optimize: {
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index a5b104b..4c22ee6 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -73,16 +73,16 @@
             </intent-filter>
         </activity-alias>
 
-        <receiver android:name="com.android.carrierdefaultapp.SliceStoreBroadcastReceiver"
+        <receiver android:name="com.android.carrierdefaultapp.SlicePurchaseBroadcastReceiver"
                   android:exported="true">
             <intent-filter>
-                <action android:name="com.android.phone.slicestore.action.START_SLICE_STORE" />
-                <action android:name="com.android.phone.slicestore.action.SLICE_STORE_RESPONSE_TIMEOUT" />
-                <action android:name="com.android.phone.slicestore.action.NOTIFICATION_CANCELED" />
+                <action android:name="com.android.phone.slice.action.START_SLICE_PURCHASE_APP" />
+                <action android:name="com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT" />
+                <action android:name="com.android.phone.slice.action.NOTIFICATION_CANCELED" />
             </intent-filter>
         </receiver>
-        <activity android:name="com.android.carrierdefaultapp.SliceStoreActivity"
-                  android:label="@string/slice_store_label"
+        <activity android:name="com.android.carrierdefaultapp.SlicePurchaseActivity"
+                  android:label="@string/slice_purchase_app_label"
                   android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
similarity index 86%
rename from packages/CarrierDefaultApp/assets/slice_store_test.html
rename to packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 7ddbd2d..67d2184 100644
--- a/packages/CarrierDefaultApp/assets/slice_store_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -20,7 +20,8 @@
     <meta charset="UTF-8">
     <meta name="description" content="
     This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
-    SliceStoreWebInterface. Test SliceStore APIs using ADB shell commands and the APIs below:
+    SlicePurchaseWebInterface. Test slice purchase application behavior using ADB shell commands and
+    the APIs below:
 
     FROM TERMINAL:
     Allow device to override carrier configs:
@@ -29,7 +30,7 @@
     $ adb shell cmd phone cc set-value -p supported_premium_capabilities_int_array 34
     Set the carrier purchase URL to this test HTML file:
     $ adb shell cmd phone cc set-value -p premium_capability_purchase_url_string \
-      file:///android_asset/slice_store_test.html
+      file:///android_asset/slice_purchase_test.html
     OPTIONAL: Allow premium capability purchase on LTE:
     $ adb shell cmd phone cc set-value -p premium_capability_supported_on_lte_bool true
     OPTIONAL: Override ServiceState to fake a NR SA connection:
@@ -43,7 +44,7 @@
             this.getMainExecutor(), request::offer);
 
     When the test application starts, this HTML will be loaded into the WebView along with the
-    associated JavaScript functions in file:///android_asset/slice_store_test.js.
+    associated JavaScript functions in file:///android_asset/slice_purchase_test.js.
     Click on the buttons in the HTML to call the corresponding @JavascriptInterface APIs.
 
     RESET DEVICE STATE:
@@ -52,11 +53,11 @@
     Clear ServiceState override that was set:
     $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
     ">
-    <title>Test SliceStoreActivity</title>
-    <script type="text/javascript" src="slice_store_test.js"></script>
+    <title>Test SlicePurchaseActivity</title>
+    <script type="text/javascript" src="slice_purchase_test.js"></script>
 </head>
 <body>
-    <h1>Test SliceStoreActivity</h1>
+    <h1>Test SlicePurchaseActivity</h1>
     <h2>Get requested premium capability</h2>
     <button type="button" onclick="testGetRequestedCapability()">
         Get requested premium capability
@@ -70,7 +71,7 @@
     <p id="purchase_successful"></p>
 
     <h2>Notify purchase failed</h2>
-    <button type="button" onclick="testNotifyPurchaseFailed()">
+    <button type="button" onclick="testNotifyPurchaseFailed(2, 'FAILURE_CODE_SERVER_UNREACHABLE')">
         Notify purchase failed
     </button>
     <p id="purchase_failed"></p>
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
similarity index 76%
rename from packages/CarrierDefaultApp/assets/slice_store_test.js
rename to packages/CarrierDefaultApp/assets/slice_purchase_test.js
index f12a6da..02c4fea 100644
--- a/packages/CarrierDefaultApp/assets/slice_store_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -15,19 +15,19 @@
  */
 
 function testGetRequestedCapability() {
-    let capability = SliceStoreWebInterface.getRequestedCapability();
+    let capability = SlicePurchaseWebInterface.getRequestedCapability();
     document.getElementById("requested_capability").innerHTML =
             "Premium capability requested: " + capability;
 }
 
 function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
-    SliceStoreWebInterface.notifyPurchaseSuccessful(duration);
+    SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
     document.getElementById("purchase_successful").innerHTML =
-            "Notified purchase success for duration: " + duration;
+            "Notified purchase success for duration: " + duration_ms_long;
 }
 
-function testNotifyPurchaseFailed() {
-    SliceStoreWebInterface.notifyPurchaseFailed();
+function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
+    SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason);
     document.getElementById("purchase_failed").innerHTML =
             "Notified purchase failed.";
 }
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index ce88a40..8a19709 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -25,6 +25,6 @@
     <!-- Notification button text to manage the network boost notification. -->
     <string name="network_boost_notification_button_manage">Manage</string>
 
-    <!-- Label to display when the slice store opens. -->
-    <string name="slice_store_label">Purchase a network boost.</string>
+    <!-- Label to display when the slice purchase application opens. -->
+    <string name="slice_purchase_app_label">Purchase a network boost.</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
new file mode 100644
index 0000000..e67ea7e
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.webkit.WebView;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that launches when the user clicks on the network boost notification.
+ * This will open a {@link WebView} for the carrier website to allow the user to complete the
+ * premium capability purchase.
+ * The carrier website can get the requested premium capability using the JavaScript interface
+ * method {@code SlicePurchaseWebInterface.getRequestedCapability()}.
+ * If the purchase is successful, the carrier website shall notify the slice purchase application
+ * using the JavaScript interface method
+ * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
+ * the optional duration of the network boost.
+ * If the purchase was not successful, the carrier website shall notify the slice purchase
+ * application using the JavaScript interface method
+ * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
+ * {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason}
+ * is the human-readable reason for failure if the failure code is
+ * {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}.
+ * If either of these notification methods are not called, the purchase cannot be completed
+ * successfully and the purchase request will eventually time out.
+ */
+public class SlicePurchaseActivity extends Activity {
+    private static final String TAG = "SlicePurchaseActivity";
+
+    private @NonNull WebView mWebView;
+    private @NonNull Context mApplicationContext;
+    private int mSubId;
+    @TelephonyManager.PremiumCapability protected int mCapability;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+        mSubId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        mCapability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        mApplicationContext = getApplicationContext();
+        URL url = getUrl();
+        logd("onCreate: subId=" + mSubId + ", capability="
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+                + ", url=" + url);
+
+        // Cancel network boost notification
+        mApplicationContext.getSystemService(NotificationManager.class)
+                .cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+
+        // Verify intent and values are valid
+        if (!SlicePurchaseBroadcastReceiver.isIntentValid(intent)) {
+            loge("Not starting SlicePurchaseActivity with an invalid Intent: " + intent);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                    intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+            finishAndRemoveTask();
+            return;
+        }
+        if (url == null) {
+            String error = "Unable to create a URL from carrier configs.";
+            loge(error);
+            Intent data = new Intent();
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
+                    SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                    getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+            finishAndRemoveTask();
+            return;
+        }
+        if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
+            loge("Unable to start the slice purchase application on the non-default data "
+                    + "subscription: " + mSubId);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                    intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB);
+            finishAndRemoveTask();
+            return;
+        }
+
+        // Create a reference to this activity in SlicePurchaseBroadcastReceiver
+        SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
+
+        // Create and configure WebView
+        mWebView = new WebView(this);
+        // Enable JavaScript for the carrier purchase website to send results back to
+        //  the slice purchase application.
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.addJavascriptInterface(
+                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+
+        // Display WebView
+        setContentView(mWebView);
+        mWebView.loadUrl(url.toString());
+    }
+
+    protected void onPurchaseSuccessful(long duration) {
+        logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+                + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."
+                : "."));
+        Intent intent = new Intent();
+        intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                getIntent(), SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+        finishAndRemoveTask();
+    }
+
+    protected void onPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
+                + failureCode + " and reason: " + failureReason);
+        Intent data = new Intent();
+        data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode);
+        data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, failureReason);
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+        finishAndRemoveTask();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+        // Pressing back in the WebView will go to the previous page instead of closing
+        //  the slice purchase application.
+        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+            mWebView.goBack();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    protected void onDestroy() {
+        logd("onDestroy: User canceled the purchase by closing the application.");
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                getIntent(), SlicePurchaseController.EXTRA_INTENT_CANCELED);
+        SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
+        super.onDestroy();
+    }
+
+    @Nullable private URL getUrl() {
+        String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
+                .getConfigForSubId(mSubId).getString(
+                        CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+        try {
+            return new URL(url);
+        } catch (MalformedURLException e) {
+            loge("Invalid URL: " + url);
+        }
+        return null;
+    }
+
+    private static void logd(@NonNull String s) {
+        Log.d(TAG, s);
+    }
+
+    private static void loge(@NonNull String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
similarity index 68%
rename from packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
rename to packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 7867ef1..5761b3c 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -33,7 +33,7 @@
 import android.util.Log;
 import android.webkit.WebView;
 
-import com.android.phone.slicestore.SliceStore;
+import com.android.phone.slice.SlicePurchaseController;
 
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
@@ -41,13 +41,14 @@
 import java.util.UUID;
 
 /**
- * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
- * SliceStore in the phone process to start the SliceStore application. It displays the network
- * boost notification to the user and will start the {@link SliceStoreActivity} to display the
+ * The SlicePurchaseBroadcastReceiver listens for
+ * {@link SlicePurchaseController#ACTION_START_SLICE_PURCHASE_APP} from the SlicePurchaseController
+ * in the phone process to start the slice purchase application. It displays the network boost
+ * notification to the user and will start the {@link SlicePurchaseActivity} to display the
  * {@link WebView} to purchase network boosts from the user's carrier.
  */
-public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
-    private static final String TAG = "SliceStoreBroadcastReceiver";
+public class SlicePurchaseBroadcastReceiver extends BroadcastReceiver{
+    private static final String TAG = "SlicePurchaseBroadcastReceiver";
 
     /**
      * UUID to report an anomaly when receiving a PendingIntent from an application or process
@@ -55,48 +56,49 @@
      */
     private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
 
-    /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
-    private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
-            new HashMap<>();
+    /** Weak references to {@link SlicePurchaseActivity} for each capability, if it exists. */
+    private static final Map<Integer, WeakReference<SlicePurchaseActivity>>
+            sSlicePurchaseActivities = new HashMap<>();
 
     /** Channel ID for the network boost notification. */
     private static final String NETWORK_BOOST_NOTIFICATION_CHANNEL_ID = "network_boost";
     /** Tag for the network boost notification. */
-    public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SliceStore.Notification";
+    public static final String NETWORK_BOOST_NOTIFICATION_TAG = "SlicePurchaseApp.Notification";
     /** Action for when the user clicks the "Not now" button on the network boost notification. */
     private static final String ACTION_NOTIFICATION_CANCELED =
-            "com.android.phone.slicestore.action.NOTIFICATION_CANCELED";
+            "com.android.phone.slice.action.NOTIFICATION_CANCELED";
 
     /**
-     * Create a weak reference to {@link SliceStoreActivity}. The reference will be removed when
-     * {@link SliceStoreActivity#onDestroy()} is called.
+     * Create a weak reference to {@link SlicePurchaseActivity}. The reference will be removed when
+     * {@link SlicePurchaseActivity#onDestroy()} is called.
      *
      * @param capability The premium capability requested.
-     * @param sliceStoreActivity The instance of SliceStoreActivity.
+     * @param slicePurchaseActivity The instance of SlicePurchaseActivity.
      */
-    public static void updateSliceStoreActivity(@TelephonyManager.PremiumCapability int capability,
-            @NonNull SliceStoreActivity sliceStoreActivity) {
-        sSliceStoreActivities.put(capability, new WeakReference<>(sliceStoreActivity));
+    public static void updateSlicePurchaseActivity(
+            @TelephonyManager.PremiumCapability int capability,
+            @NonNull SlicePurchaseActivity slicePurchaseActivity) {
+        sSlicePurchaseActivities.put(capability, new WeakReference<>(slicePurchaseActivity));
     }
 
     /**
-     * Remove the weak reference to {@link SliceStoreActivity} when
-     * {@link SliceStoreActivity#onDestroy()} is called.
+     * Remove the weak reference to {@link SlicePurchaseActivity} when
+     * {@link SlicePurchaseActivity#onDestroy()} is called.
      *
      * @param capability The premium capability requested.
      */
-    public static void removeSliceStoreActivity(
+    public static void removeSlicePurchaseActivity(
             @TelephonyManager.PremiumCapability int capability) {
-        sSliceStoreActivities.remove(capability);
+        sSlicePurchaseActivities.remove(capability);
     }
 
     /**
-     * Send the PendingIntent containing the corresponding SliceStore response.
+     * Send the PendingIntent containing the corresponding slice purchase application response.
      *
      * @param intent The Intent containing the PendingIntent extra.
      * @param extra The extra to get the PendingIntent to send.
      */
-    public static void sendSliceStoreResponse(@NonNull Intent intent, @NonNull String extra) {
+    public static void sendSlicePurchaseAppResponse(@NonNull Intent intent, @NonNull String extra) {
         PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
         if (pendingIntent == null) {
             loge("PendingIntent does not exist for extra: " + extra);
@@ -110,14 +112,15 @@
     }
 
     /**
-     * Send the PendingIntent containing the corresponding SliceStore response with additional data.
+     * Send the PendingIntent containing the corresponding slice purchase application response
+     * with additional data.
      *
      * @param context The Context to use to send the PendingIntent.
      * @param intent The Intent containing the PendingIntent extra.
      * @param extra The extra to get the PendingIntent to send.
      * @param data The Intent containing additional data to send with the PendingIntent.
      */
-    public static void sendSliceStoreResponseWithData(@NonNull Context context,
+    public static void sendSlicePurchaseAppResponseWithData(@NonNull Context context,
             @NonNull Intent intent, @NonNull String extra, @NonNull Intent data) {
         PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
         if (pendingIntent == null) {
@@ -132,45 +135,46 @@
     }
 
     /**
-     * Check whether the Intent is valid and can be used to complete purchases in the SliceStore.
-     * This checks that all necessary extras exist and that the values are valid.
+     * Check whether the Intent is valid and can be used to complete purchases in the slice purchase
+     * application. This checks that all necessary extras exist and that the values are valid.
      *
      * @param intent The intent to check
      * @return {@code true} if the intent is valid and {@code false} otherwise.
      */
     public static boolean isIntentValid(@NonNull Intent intent) {
-        int phoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
+        int phoneId = intent.getIntExtra(SlicePurchaseController.EXTRA_PHONE_ID,
                 SubscriptionManager.INVALID_PHONE_INDEX);
         if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
             loge("isIntentValid: invalid phone index: " + phoneId);
             return false;
         }
 
-        int subId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
+        int subId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             loge("isIntentValid: invalid subscription ID: " + subId);
             return false;
         }
 
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
-        if (capability == SliceStore.PREMIUM_CAPABILITY_INVALID) {
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
+        if (capability == SlicePurchaseController.PREMIUM_CAPABILITY_INVALID) {
             loge("isIntentValid: invalid premium capability: " + capability);
             return false;
         }
 
-        String appName = intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME);
+        String appName = intent.getStringExtra(SlicePurchaseController.EXTRA_REQUESTING_APP_NAME);
         if (TextUtils.isEmpty(appName)) {
             loge("isIntentValid: empty requesting application name: " + appName);
             return false;
         }
 
-        return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED)
-                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR)
-                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED)
-                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA)
-                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_SUCCESS);
+        return isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED)
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR)
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
+                && isPendingIntentValid(intent,
+                        SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB)
+                && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
     }
 
     private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
@@ -197,11 +201,12 @@
 
     @NonNull private static String getPendingIntentType(@NonNull String extra) {
         switch (extra) {
-            case SliceStore.EXTRA_INTENT_CANCELED: return "canceled";
-            case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
-            case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
-            case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data";
-            case SliceStore.EXTRA_INTENT_SUCCESS: return "success";
+            case SlicePurchaseController.EXTRA_INTENT_CANCELED: return "canceled";
+            case SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
+            case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
+            case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUB:
+                return "not default data sub";
+            case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
             default: {
                 loge("Unknown pending intent extra: " + extra);
                 return "unknown(" + extra + ")";
@@ -213,10 +218,10 @@
     public void onReceive(@NonNull Context context, @NonNull Intent intent) {
         logd("onReceive intent: " + intent.getAction());
         switch (intent.getAction()) {
-            case SliceStore.ACTION_START_SLICE_STORE:
+            case SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP:
                 onDisplayBoosterNotification(context, intent);
                 break;
-            case SliceStore.ACTION_SLICE_STORE_RESPONSE_TIMEOUT:
+            case SlicePurchaseController.ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT:
                 onTimeout(context, intent);
                 break;
             case ACTION_NOTIFICATION_CANCELED:
@@ -229,7 +234,8 @@
 
     private void onDisplayBoosterNotification(@NonNull Context context, @NonNull Intent intent) {
         if (!isIntentValid(intent)) {
-            sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED);
+            sendSlicePurchaseAppResponse(intent,
+                    SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
             return;
         }
 
@@ -243,13 +249,14 @@
                 new Notification.Builder(context, NETWORK_BOOST_NOTIFICATION_CHANNEL_ID)
                         .setContentTitle(String.format(context.getResources().getString(
                                 R.string.network_boost_notification_title),
-                                intent.getStringExtra(SliceStore.EXTRA_REQUESTING_APP_NAME)))
+                                intent.getStringExtra(
+                                        SlicePurchaseController.EXTRA_REQUESTING_APP_NAME)))
                         .setContentText(context.getResources().getString(
                                 R.string.network_boost_notification_detail))
                         .setSmallIcon(R.drawable.ic_network_boost)
                         .setContentIntent(createContentIntent(context, intent, 1))
                         .setDeleteIntent(intent.getParcelableExtra(
-                                SliceStore.EXTRA_INTENT_CANCELED, PendingIntent.class))
+                                SlicePurchaseController.EXTRA_INTENT_CANCELED, PendingIntent.class))
                         // Add an action for the "Not now" button, which has the same behavior as
                         // the user canceling or closing the notification.
                         .addAction(new Notification.Action.Builder(
@@ -266,8 +273,8 @@
                                 createContentIntent(context, intent, 2)).build())
                         .build();
 
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         logd("Display the booster notification for capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability));
         context.getSystemService(NotificationManager.class).notifyAsUser(
@@ -276,19 +283,19 @@
 
     /**
      * Create the intent for when the user clicks on the "Manage" button on the network boost
-     * notification or the notification itself. This will open {@link SliceStoreActivity}.
+     * notification or the notification itself. This will open {@link SlicePurchaseActivity}.
      *
      * @param context The Context to create the intent for.
-     * @param intent The source Intent used to launch the SliceStore application.
+     * @param intent The source Intent used to launch the slice purchase application.
      * @param requestCode The request code for the PendingIntent.
      *
-     * @return The intent to start {@link SliceStoreActivity}.
+     * @return The intent to start {@link SlicePurchaseActivity}.
      */
     @NonNull private PendingIntent createContentIntent(@NonNull Context context,
             @NonNull Intent intent, int requestCode) {
-        Intent i = new Intent(context, SliceStoreActivity.class);
+        Intent i = new Intent(context, SlicePurchaseActivity.class);
         i.setComponent(ComponentName.unflattenFromString(
-                "com.android.carrierdefaultapp/.SliceStoreActivity"));
+                "com.android.carrierdefaultapp/.SlicePurchaseActivity"));
         i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
                 | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         i.putExtras(intent);
@@ -303,7 +310,7 @@
      * as if the user had canceled or removed the notification.
      *
      * @param context The Context to create the intent for.
-     * @param intent The source Intent used to launch the SliceStore application.
+     * @param intent The source Intent used to launch the slice purchase application.
      *
      * @return The canceled intent.
      */
@@ -311,37 +318,37 @@
             @NonNull Intent intent) {
         Intent i = new Intent(ACTION_NOTIFICATION_CANCELED);
         i.setComponent(ComponentName.unflattenFromString(
-                "com.android.carrierdefaultapp/.SliceStoreBroadcastReceiver"));
+                "com.android.carrierdefaultapp/.SlicePurchaseBroadcastReceiver"));
         i.putExtras(intent);
         return PendingIntent.getBroadcast(context, 0, i,
                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
     }
 
     private void onTimeout(@NonNull Context context, @NonNull Intent intent) {
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         logd("Purchase capability " + TelephonyManager.convertPremiumCapabilityToString(capability)
                 + " timed out.");
-        if (sSliceStoreActivities.get(capability) == null) {
+        if (sSlicePurchaseActivities.get(capability) == null) {
             // Notification is still active
             logd("Closing booster notification since the user did not respond in time.");
             context.getSystemService(NotificationManager.class).cancelAsUser(
                     NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
         } else {
-            // Notification was dismissed but SliceStoreActivity is still active
-            logd("Closing SliceStore WebView since the user did not complete the purchase "
-                    + "in time.");
-            sSliceStoreActivities.get(capability).get().finishAndRemoveTask();
+            // Notification was dismissed but SlicePurchaseActivity is still active
+            logd("Closing slice purchase application WebView since the user did not complete the "
+                    + "purchase in time.");
+            sSlicePurchaseActivities.get(capability).get().finishAndRemoveTask();
         }
     }
 
     private void onUserCanceled(@NonNull Context context, @NonNull Intent intent) {
-        int capability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
+        int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         logd("onUserCanceled: " + TelephonyManager.convertPremiumCapabilityToString(capability));
         context.getSystemService(NotificationManager.class)
                 .cancelAsUser(NETWORK_BOOST_NOTIFICATION_TAG, capability, UserHandle.ALL);
-        sendSliceStoreResponse(intent, SliceStore.EXTRA_INTENT_CANCELED);
+        sendSlicePurchaseAppResponse(intent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
     }
 
     private static void logd(String s) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
similarity index 67%
rename from packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
rename to packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
index ab5d080..8547898 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
@@ -21,18 +21,19 @@
 import android.telephony.TelephonyManager;
 import android.webkit.JavascriptInterface;
 
-import com.android.phone.slicestore.SliceStore;
+import com.android.phone.slice.SlicePurchaseController;
 
 /**
- * SliceStore web interface class allowing carrier websites to send responses back to SliceStore
- * using JavaScript.
+ * Slice purchase web interface class allowing carrier websites to send responses back to the
+ * slice purchase application using JavaScript.
  */
-public class SliceStoreWebInterface {
-    @NonNull SliceStoreActivity mActivity;
+public class SlicePurchaseWebInterface {
+    @NonNull SlicePurchaseActivity mActivity;
 
-    public SliceStoreWebInterface(@NonNull SliceStoreActivity activity) {
+    public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity activity) {
         mActivity = activity;
     }
+
     /**
      * Interface method allowing the carrier website to get the premium capability
      * that was requested to purchase.
@@ -40,7 +41,7 @@
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
      *     function getRequestedCapability(duration) {
-     *         SliceStoreWebInterface.getRequestedCapability();
+     *         SlicePurchaseWebInterface.getRequestedCapability();
      *     }
      * </script>
      */
@@ -50,13 +51,14 @@
     }
 
     /**
-     * Interface method allowing the carrier website to notify the SliceStore of a successful
-     * premium capability purchase and the duration for which the premium capability is purchased.
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a successful premium capability purchase and the duration for which the premium capability is
+     * purchased.
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
      *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
-     *         SliceStoreWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+     *         SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
      *     }
      * </script>
      *
@@ -68,22 +70,23 @@
     }
 
     /**
-     * Interface method allowing the carrier website to notify the SliceStore of a failed
-     * premium capability purchase.
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a failed premium capability purchase.
      *
      * This can be called using the JavaScript below:
      * <script type="text/javascript">
-     *     function notifyPurchaseFailed() {
-     *         SliceStoreWebInterface.notifyPurchaseFailed();
+     *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
+     *         SlicePurchaseWebInterface.notifyPurchaseFailed();
      *     }
      * </script>
      *
      * @param failureCode The failure code.
-     * @param failureReason If the failure code is {@link SliceStore#FAILURE_CODE_UNKNOWN},
+     * @param failureReason If the failure code is
+     *                      {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN},
      *                      the human-readable reason for failure.
      */
     @JavascriptInterface
-    public void notifyPurchaseFailed(@SliceStore.FailureCode int failureCode,
+    public void notifyPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
             @Nullable String failureReason) {
         mActivity.onPurchaseFailed(failureCode, failureReason);
     }
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
deleted file mode 100644
index 348e389..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ /dev/null
@@ -1,184 +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.carrierdefaultapp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.webkit.WebView;
-
-import com.android.phone.slicestore.SliceStore;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that launches when the user clicks on the network boost notification.
- * This will open a {@link WebView} for the carrier website to allow the user to complete the
- * premium capability purchase.
- * The carrier website can get the requested premium capability using the JavaScript interface
- * method {@code SliceStoreWebInterface.getRequestedCapability()}.
- * If the purchase is successful, the carrier website shall notify SliceStore using the JavaScript
- * interface method {@code SliceStoreWebInterface.notifyPurchaseSuccessful(duration)}, where
- * {@code duration} is the duration of the network boost.
- * If the purchase was not successful, the carrier website shall notify SliceStore using the
- * JavaScript interface method {@code SliceStoreWebInterface.notifyPurchaseFailed()}.
- * If either of these notification methods are not called, the purchase cannot be completed
- * successfully and the purchase request will eventually time out.
- */
-public class SliceStoreActivity extends Activity {
-    private static final String TAG = "SliceStoreActivity";
-
-    private @NonNull WebView mWebView;
-    private @NonNull Context mApplicationContext;
-    private int mSubId;
-    @TelephonyManager.PremiumCapability protected int mCapability;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-        mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
-                SliceStore.PREMIUM_CAPABILITY_INVALID);
-        mApplicationContext = getApplicationContext();
-        URL url = getUrl();
-        logd("onCreate: subId=" + mSubId + ", capability="
-                + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + ", url=" + url);
-
-        // Cancel network boost notification
-        mApplicationContext.getSystemService(NotificationManager.class)
-                .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
-
-        // Verify intent and values are valid
-        if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
-            loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED);
-            finishAndRemoveTask();
-            return;
-        }
-        if (url == null) {
-            String error = "Unable to create a URL from carrier configs.";
-            loge(error);
-            Intent data = new Intent();
-            data.putExtra(SliceStore.EXTRA_FAILURE_CODE,
-                    SliceStore.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
-            data.putExtra(SliceStore.EXTRA_FAILURE_REASON, error);
-            SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
-                    mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
-            finishAndRemoveTask();
-            return;
-        }
-        if (mSubId != SubscriptionManager.getDefaultSubscriptionId()) {
-            loge("Unable to start SliceStore on the non-default data subscription: " + mSubId);
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
-            finishAndRemoveTask();
-            return;
-        }
-
-        // Create a reference to this activity in SliceStoreBroadcastReceiver
-        SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
-
-        // Create and configure WebView
-        mWebView = new WebView(this);
-        // Enable JavaScript for the carrier purchase website to send results back to SliceStore
-        mWebView.getSettings().setJavaScriptEnabled(true);
-        mWebView.addJavascriptInterface(new SliceStoreWebInterface(this), "SliceStoreWebInterface");
-
-        // Display WebView
-        setContentView(mWebView);
-        mWebView.loadUrl(url.toString());
-    }
-
-    protected void onPurchaseSuccessful(long duration) {
-        logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
-                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes.");
-        Intent intent = new Intent();
-        intent.putExtra(SliceStore.EXTRA_PURCHASE_DURATION, duration);
-        SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
-                mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_SUCCESS, intent);
-        finishAndRemoveTask();
-    }
-
-    protected void onPurchaseFailed(@SliceStore.FailureCode int failureCode,
-            @Nullable String failureReason) {
-        logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
-                + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
-                + SliceStore.convertFailureCodeToString(failureCode) + " and reason: "
-                + failureReason);
-        Intent data = new Intent();
-        data.putExtra(SliceStore.EXTRA_FAILURE_CODE, failureCode);
-        data.putExtra(SliceStore.EXTRA_FAILURE_REASON, failureReason);
-        SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
-                mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
-        finishAndRemoveTask();
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
-        // Pressing back in the WebView will go to the previous page instead of closing SliceStore.
-        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
-            mWebView.goBack();
-            return true;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    @Override
-    protected void onDestroy() {
-        logd("onDestroy: User canceled the purchase by closing the application.");
-        SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                getIntent(), SliceStore.EXTRA_INTENT_CANCELED);
-        SliceStoreBroadcastReceiver.removeSliceStoreActivity(mCapability);
-        super.onDestroy();
-    }
-
-    @Nullable private URL getUrl() {
-        String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
-                .getConfigForSubId(mSubId).getString(
-                        CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
-        try {
-            return new URL(url);
-        } catch (MalformedURLException e) {
-            loge("Invalid URL: " + url);
-        }
-        return null;
-    }
-
-    private static void logd(@NonNull String s) {
-        Log.d(TAG, s);
-    }
-
-    private static void loge(@NonNull String s) {
-        Log.e(TAG, s);
-    }
-}
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 25529bb..d8577c3 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -17,7 +17,11 @@
     static_libs: [
         "androidx.activity_activity-compose",
         "androidx.appcompat_appcompat",
-        "androidx.compose.material_material",
+        "androidx.compose.animation_animation-core",
+        "androidx.compose.foundation_foundation",
+        "androidx.compose.material3_material3",
+        "androidx.compose.material_material-icons-core",
+        "androidx.compose.material_material-icons-extended",
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui",
         "androidx.compose.ui_ui-tooling",
@@ -27,6 +31,7 @@
         "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.lifecycle_lifecycle-viewmodel-compose",
         "androidx.recyclerview_recyclerview",
+        "kotlinx-coroutines-core",
     ],
 
     platform_apis: true,
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..ae65940
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_profile.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+        android:viewportWidth="46"
+        android:viewportHeight="46"
+        android:width="46dp"
+        android:height="46dp">
+    <path
+        android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
+        android:strokeColor="#202124"
+        android:strokeAlpha="0.13"
+        android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index c6779fa..7a4b7cb 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -22,4 +22,33 @@
   <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
   <string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
   <string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
+  <string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
+  <string name="passkeys">passkeys</string>
+  <string name="passwords">passwords</string>
+  <string name="sign_ins">sign-ins</string>
+  <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
+  <!-- Spoken content description of an element which will close the sheet when clicked. -->
+  <string name="close_sheet">"Close sheet"</string>
+  <!-- Spoken content description of the back arrow button. -->
+  <string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
+
+  <!-- Strings for the get flow. -->
+  <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the dialog asking for user confirmation to use the single previously saved credential to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+  <!-- Appears as an option row for viewing all the available sign-in options. [CHAR LIMIT=80] -->
+  <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
+  <!-- Button label to close the dialog when the user does not want to use any sign-in. [CHAR LIMIT=40] -->
+  <string name="get_dialog_button_label_no_thanks">No thanks</string>
+  <!-- Button label to continue with the selected sign-in. [CHAR LIMIT=40] -->
+  <string name="get_dialog_button_label_continue">Continue</string>
+  <!-- Separator for sign-in type and username in a sign-in entry. -->
+  <string name="get_dialog_sign_in_type_username_separator" translatable="false">" - "</string>
+  <!-- Modal bottom sheet title for displaying all the available sign-in options. [CHAR LIMIT=80] -->
+  <string name="get_dialog_title_sign_in_options">Sign-in options</string>
+  <!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=20] -->
+  <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
deleted file mode 100644
index ee4f4ca..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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?,
-  val usageData: CharSequence?,
-  // 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
-      var usageData: String? = null
-
-      items.forEach {
-        if (it.hasHint(Entry.HINT_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
-          subTitle = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_TITLE)) {
-          title = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
-          usageData = it.text.toString()
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return CredentialEntryUi(title!!, subTitle, icon, usageData)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 01348e4..2099a23 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -16,11 +16,14 @@
 
 package com.android.credentialmanager
 
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
 import android.app.slice.Slice
 import android.app.slice.SliceSpec
 import android.content.Context
 import android.content.Intent
 import android.credentials.CreateCredentialRequest
+import android.credentials.GetCredentialOption
+import android.credentials.GetCredentialRequest
 import android.credentials.ui.Constants
 import android.credentials.ui.Entry
 import android.credentials.ui.CreateCredentialProviderData
@@ -33,12 +36,14 @@
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
+import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreatePasskeyUiState
 import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ProviderInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 
 // Consider repo per screen, similar to view model?
 class CredentialManagerRepo(
@@ -54,7 +59,7 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testRequestInfo()
+    ) ?: testCreateRequestInfo()
 
     providerList = when (requestInfo.type) {
       RequestInfo.TYPE_CREATE ->
@@ -99,19 +104,14 @@
 
   fun getCredentialInitialUiState(): GetCredentialUiState {
     val providerList = GetFlowUtils.toProviderList(
-      // TODO: handle runtime cast error
-      providerList as List<GetCredentialProviderData>, context)
+    // TODO: handle runtime cast error
+    providerList as List<GetCredentialProviderData>, context)
     // TODO: covert from real requestInfo
-    val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo(
-      "Elisa Beckett",
-      "beckett-bakert@gmail.com",
-      TYPE_PUBLIC_KEY_CREDENTIAL,
-      "tribank")
+    val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("tribank")
     return GetCredentialUiState(
       providerList,
-      GetScreenState.CREDENTIAL_SELECTION,
+      GetScreenState.PRIMARY_SELECTION,
       requestDisplayInfo,
-      providerList.first()
     )
   }
 
@@ -119,6 +119,11 @@
     val providerList = CreateFlowUtils.toProviderList(
       // Handle runtime cast error
       providerList as List<CreateCredentialProviderData>, context)
+    var hasDefault = false
+    var defaultProvider: ProviderInfo = providerList.first()
+    providerList.forEach{providerInfo -> providerInfo.createOptions =
+      providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
+      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
     // TODO: covert from real requestInfo
     val requestDisplayInfo = RequestDisplayInfo(
       "Elisa Beckett",
@@ -127,8 +132,12 @@
       "tribank")
     return CreatePasskeyUiState(
       providers = providerList,
-      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+      if (hasDefault)
+      {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
       requestDisplayInfo,
+      if (hasDefault) {
+        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+      } else null
     )
   }
 
@@ -150,40 +159,29 @@
   // TODO: below are prototype functionalities. To be removed for productionization.
   private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
     return listOf(
-      CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+      CreateCredentialProviderData
+        .Builder("com.google/com.google.CredentialManagerService")
         .setSaveEntries(
           listOf<Entry>(
-            newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
-            newEntry("key1", "subkey-2", "elisa.work@google.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+            newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+              20, 7, 27, 10000),
+            newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
+              20, 7, 27, 11000),
           )
         )
-        .setActionChips(
-          listOf<Entry>(
-            newEntry("key2", "subkey-1", "Go to Settings", "",
-                     "20 passwords and 7 passkeys saved"),
-            newEntry("key2", "subkey-2", "Switch Account", "",
-                     "20 passwords and 7 passkeys saved"),
-          ),
-        )
         .setIsDefaultProvider(true)
         .build(),
-      CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+      CreateCredentialProviderData
+        .Builder("com.dashlane/com.dashlane.CredentialManagerService")
         .setSaveEntries(
           listOf<Entry>(
-            newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
-            newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+            newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+              20, 7, 27, 30000),
+            newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+              20, 7, 27, 31000),
           )
-        ).setActionChips(
-          listOf<Entry>(
-            newEntry("key2", "subkey-3", "Manage Accounts",
-              "Manage your accounts in the dashlane app",
-                     "20 passwords and 7 passkeys saved"),
-          ),
-        ).build(),
+        )
+        .build(),
     )
   }
 
@@ -192,54 +190,97 @@
       GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
         .setCredentialEntries(
           listOf<Entry>(
-            newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
-            newEntry("key1", "subkey-2", "elisa.work@google.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+            newGetEntry(
+              "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+              "elisa.bakery@gmail.com", "Elisa Beckett", 300L
+            ),
+            newGetEntry(
+              "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.bakery@gmail.com", null, 300L
+            ),
+            newGetEntry(
+              "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.family@outlook.com", null, 100L
+            ),
           )
-        ).setActionChips(
-          listOf<Entry>(
-            newEntry("key2", "subkey-1", "Go to Settings", "",
-              "20 passwords and 7 passkeys saved"),
-            newEntry("key2", "subkey-2", "Switch Account", "",
-              "20 passwords and 7 passkeys saved"),
-          ),
         ).build(),
       GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
         .setCredentialEntries(
           listOf<Entry>(
-            newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
-            newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+            newGetEntry(
+              "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.family@outlook.com", null, 600L
+            ),
+            newGetEntry(
+              "key1", "subkey-2", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+              "elisa.family@outlook.com", null, 100L
+            ),
           )
-        ).setActionChips(
-          listOf<Entry>(
-            newEntry("key2", "subkey-3", "Manage Accounts",
-              "Manage your accounts in the dashlane app",
-              "20 passwords and 7 passkeys saved"),
-          ),
         ).build(),
     )
   }
 
-  private fun newEntry(
+  private fun newGetEntry(
     key: String,
     subkey: String,
-    title: String,
-    subtitle: String,
-    usageData: String
+    credentialType: String,
+    credentialTypeDisplayName: String,
+    userName: String,
+    userDisplayName: String?,
+    lastUsedTimeMillis: Long?,
+  ): Entry {
+    val slice = Slice.Builder(
+      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+    ).addText(
+      credentialTypeDisplayName, null, listOf(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)
+    ).addText(
+      userName, null, listOf(Entry.HINT_USER_NAME)
+    ).addIcon(
+      Icon.createWithResource(context, R.drawable.ic_passkey),
+      null,
+      listOf(Entry.HINT_PROFILE_ICON))
+    if (userDisplayName != null) {
+      slice.addText(userDisplayName, null, listOf(Entry.HINT_PASSKEY_USER_DISPLAY_NAME))
+    }
+    if (lastUsedTimeMillis != null) {
+      slice.addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
+    }
+    return Entry(
+      key,
+      subkey,
+      slice.build()
+    )
+  }
+
+  private fun newCreateEntry(
+    key: String,
+    subkey: String,
+    providerDisplayName: String,
+    passwordCount: Int,
+    passkeyCount: Int,
+    totalCredentialCount: Int,
+    lastUsedTimeMillis: Long,
   ): Entry {
     val slice = Slice.Builder(
       Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
     )
-      .addText(title, null, listOf(Entry.HINT_TITLE))
-      .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+      .addText(
+        providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
       .addIcon(
         Icon.createWithResource(context, R.drawable.ic_passkey),
         null,
-        listOf(Entry.HINT_ICON))
-      .addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
+        listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
+      .addIcon(
+        Icon.createWithResource(context, R.drawable.ic_profile),
+        null,
+        listOf(Entry.HINT_PROFILE_ICON))
+      .addInt(
+        passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
+      .addInt(
+        passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
+      .addInt(
+        totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
+      .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
       .build()
     return Entry(
       key,
@@ -248,12 +289,11 @@
     )
   }
 
-  private fun testRequestInfo(): RequestInfo {
+  private fun testCreateRequestInfo(): RequestInfo {
     val data = Bundle()
     return RequestInfo.newCreateRequestInfo(
       Binder(),
       CreateCredentialRequest(
-        // TODO: use the jetpack type and utils once defined.
         TYPE_PUBLIC_KEY_CREDENTIAL,
         data
       ),
@@ -261,4 +301,18 @@
       "tribank.us"
     )
   }
+
+  private fun testGetRequestInfo(): RequestInfo {
+    val data = Bundle()
+    return RequestInfo.newGetRequestInfo(
+      Binder(),
+      GetCredentialRequest.Builder()
+        .addGetCredentialOption(
+          GetCredentialOption(TYPE_PUBLIC_KEY_CREDENTIAL, Bundle())
+        )
+        .build(),
+      /*isFirstUsage=*/false,
+      "tribank.us"
+    )
+  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index bf0dba2..5c79564 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -21,8 +21,12 @@
 import android.credentials.ui.GetCredentialProviderData
 import android.credentials.ui.CreateCredentialProviderData
 import com.android.credentialmanager.createflow.CreateOptionInfo
-import com.android.credentialmanager.getflow.CredentialOptionInfo
+import com.android.credentialmanager.getflow.ActionEntryInfo
+import com.android.credentialmanager.getflow.AuthenticationEntryInfo
+import com.android.credentialmanager.getflow.CredentialEntryInfo
 import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
+import com.android.credentialmanager.jetpack.provider.SaveEntryUi
 
 /** Utility functions for converting CredentialManager data structures to or from UI formats. */
 class GetFlowUtils {
@@ -34,39 +38,65 @@
     ): List<ProviderInfo> {
       return providerDataList.map {
         ProviderInfo(
+          id = it.providerFlattenedComponentName,
           // TODO: replace to extract from the service data structure when available
           icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = it.providerFlattenedComponentName,
           // TODO: get the service display name and icon from the component name.
           displayName = it.providerFlattenedComponentName,
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context),
+          credentialEntryList = getCredentialOptionInfoList(
+            it.providerFlattenedComponentName, it.credentialEntries, context),
+          authenticationEntry = getAuthenticationEntry(
+            it.providerFlattenedComponentName, it.authenticationEntry, context),
+          actionEntryList = getActionEntryList(
+            it.providerFlattenedComponentName, it.actionChips, context),
         )
       }
     }
 
 
     /* From service data structure to UI credential entry list representation. */
-    private fun toCredentialOptionInfoList(
+    private fun getCredentialOptionInfoList(
+      providerId: String,
       credentialEntries: List<Entry>,
       context: Context,
-    ): List<CredentialOptionInfo> {
+    ): List<CredentialEntryInfo> {
       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",
+        return@map CredentialEntryInfo(
+          providerId = providerId,
           entryKey = it.key,
           entrySubkey = it.subkey,
-          usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
+          credentialType = credentialEntryUi.credentialType.toString(),
+          credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
+          userName = credentialEntryUi.userName.toString(),
+          displayName = credentialEntryUi.userDisplayName?.toString(),
+          // TODO: proper fallback
+          icon = credentialEntryUi.entryIcon.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
         )
       }
     }
+
+    private fun getAuthenticationEntry(
+      providerId: String,
+      authEntry: Entry?,
+      context: Context,
+    ): AuthenticationEntryInfo? {
+      // TODO: implement
+      return null
+    }
+
+    private fun getActionEntryList(
+      providerId: String,
+      actionEntries: List<Entry>,
+      context: Context,
+    ): List<ActionEntryInfo> {
+      // TODO: implement
+      return emptyList()
+    }
   }
 }
 
@@ -82,9 +112,7 @@
           // TODO: replace to extract from the service data structure when available
           icon = context.getDrawable(R.drawable.ic_passkey)!!,
           name = it.providerFlattenedComponentName,
-          // TODO: get the service display name and icon from the component name.
           displayName = it.providerFlattenedComponentName,
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
           createOptions = toCreationOptionInfoList(it.saveEntries, context),
           isDefault = it.isDefaultProvider,
         )
@@ -100,13 +128,17 @@
 
         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",
           entryKey = it.key,
           entrySubkey = it.subkey,
-          usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
+          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
+          credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_profile)!!,
+          passwordCount = saveEntryUi.passwordCount ?: 0,
+          passkeyCount = saveEntryUi.passkeyCount ?: 0,
+          totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
+          lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
         )
       }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
deleted file mode 100644
index cd52197..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.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?,
-  val usageData: CharSequence?,
-  // 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
-      var usageData: String? = null
-
-      items.forEach {
-        if (it.hasHint(Entry.HINT_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
-          subTitle = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_TITLE)) {
-          title = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
-          usageData = it.text.toString()
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return SaveEntryUi(title!!, subTitle, icon, usageData)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
new file mode 100644
index 0000000..f1f453d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.material
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.collapse
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.dismiss
+import androidx.compose.ui.semantics.expand
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.launch
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+/**
+ * Possible values of [ModalBottomSheetState].
+ */
+enum class ModalBottomSheetValue {
+    /**
+     * The bottom sheet is not visible.
+     */
+    Hidden,
+
+    /**
+     * The bottom sheet is visible at full height.
+     */
+    Expanded,
+
+    /**
+     * The bottom sheet is partially visible at 50% of the screen height. This state is only
+     * enabled if the height of the bottom sheet is more than 50% of the screen height.
+     */
+    HalfExpanded
+}
+
+/**
+ * State of the [ModalBottomSheetLayout] composable.
+ *
+ * @param initialValue The initial value of the state. <b>Must not be set to
+ * [ModalBottomSheetValue.HalfExpanded] if [isSkipHalfExpanded] is set to true.</b>
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param isSkipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * <b>Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded].</b>
+ * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an
+ * [IllegalArgumentException] will be thrown.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+class ModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+    internal val isSkipHalfExpanded: Boolean,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+) : SwipeableState<ModalBottomSheetValue>(
+    initialValue = initialValue,
+    animationSpec = animationSpec,
+    confirmStateChange = confirmStateChange
+) {
+    /**
+     * Whether the bottom sheet is visible.
+     */
+    val isVisible: Boolean
+        get() = currentValue != Hidden
+
+    internal val hasHalfExpandedState: Boolean
+        get() = anchors.values.contains(HalfExpanded)
+
+    constructor(
+        initialValue: ModalBottomSheetValue,
+        animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+        confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+    ) : this(initialValue, animationSpec, isSkipHalfExpanded = false, confirmStateChange)
+
+    init {
+        if (isSkipHalfExpanded) {
+            require(initialValue != HalfExpanded) {
+                "The initial value must not be set to HalfExpanded if skipHalfExpanded is set to" +
+                        " true."
+            }
+        }
+    }
+
+    /**
+     * Show the bottom sheet with animation and suspend until it's shown. If the sheet is taller
+     * than 50% of the parent's height, the bottom sheet will be half expanded. Otherwise it will be
+     * fully expanded.
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun show() {
+        val targetValue = when {
+            hasHalfExpandedState -> HalfExpanded
+            else -> Expanded
+        }
+        animateTo(targetValue = targetValue)
+    }
+
+    /**
+     * Half expand the bottom sheet if half expand is enabled with animation and suspend until it
+     * animation is complete or cancelled
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    internal suspend fun halfExpand() {
+        if (!hasHalfExpandedState) {
+            return
+        }
+        animateTo(HalfExpanded)
+    }
+
+    /**
+     * Fully expand the bottom sheet with animation and suspend until it if fully expanded or
+     * animation has been cancelled.
+     * *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    internal suspend fun expand() = animateTo(Expanded)
+
+    /**
+     * Hide the bottom sheet with animation and suspend until it if fully hidden or animation has
+     * been cancelled.
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun hide() = animateTo(Hidden)
+
+    internal val nestedScrollConnection = this.PreUpPostDownNestedScrollConnection
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [ModalBottomSheetState].
+         */
+        fun Saver(
+            animationSpec: AnimationSpec<Float>,
+            skipHalfExpanded: Boolean,
+            confirmStateChange: (ModalBottomSheetValue) -> Boolean
+        ): Saver<ModalBottomSheetState, *> = Saver(
+            save = { it.currentValue },
+            restore = {
+                ModalBottomSheetState(
+                    initialValue = it,
+                    animationSpec = animationSpec,
+                    isSkipHalfExpanded = skipHalfExpanded,
+                    confirmStateChange = confirmStateChange
+                )
+            }
+        )
+
+        /**
+         * The default [Saver] implementation for [ModalBottomSheetState].
+         */
+        @Deprecated(
+            message = "Please specify the skipHalfExpanded parameter",
+            replaceWith = ReplaceWith(
+                "ModalBottomSheetState.Saver(" +
+                        "animationSpec = animationSpec," +
+                        "skipHalfExpanded = ," +
+                        "confirmStateChange = confirmStateChange" +
+                        ")"
+            )
+        )
+        fun Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (ModalBottomSheetValue) -> Boolean
+        ): Saver<ModalBottomSheetState, *> = Saver(
+            animationSpec = animationSpec,
+            skipHalfExpanded = false,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param skipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * <b>Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded].</b>
+ * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an
+ * [IllegalArgumentException] will be thrown.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun rememberModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+    skipHalfExpanded: Boolean,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState {
+    return rememberSaveable(
+        initialValue, animationSpec, skipHalfExpanded, confirmStateChange,
+        saver = ModalBottomSheetState.Saver(
+            animationSpec = animationSpec,
+            skipHalfExpanded = skipHalfExpanded,
+            confirmStateChange = confirmStateChange
+        )
+    ) {
+        ModalBottomSheetState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            isSkipHalfExpanded = skipHalfExpanded,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun rememberModalBottomSheetState(
+    initialValue: ModalBottomSheetValue,
+    animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+    confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState = rememberModalBottomSheetState(
+    initialValue = initialValue,
+    animationSpec = animationSpec,
+    skipHalfExpanded = false,
+    confirmStateChange = confirmStateChange
+)
+
+/**
+ * <a href="https://material.io/components/sheets-bottom#modal-bottom-sheet" class="external" target="_blank">Material Design modal bottom sheet</a>.
+ *
+ * Modal bottom sheets present a set of choices while blocking interaction with the rest of the
+ * screen. They are an alternative to inline menus and simple dialogs, providing
+ * additional room for content, iconography, and actions.
+ *
+ * ![Modal bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material/modal-bottom-sheet.png)
+ *
+ * A simple example of a modal bottom sheet looks like this:
+ *
+ * @sample androidx.compose.material.samples.ModalBottomSheetSample
+ *
+ * @param sheetContent The content of the bottom sheet.
+ * @param modifier Optional [Modifier] for the entire component.
+ * @param sheetState The state of the bottom sheet.
+ * @param sheetShape The shape of the bottom sheet.
+ * @param sheetElevation The elevation of the bottom sheet.
+ * @param sheetBackgroundColor The background color of the bottom sheet.
+ * @param sheetContentColor The preferred content color provided by the bottom sheet to its
+ * children. Defaults to the matching content color for [sheetBackgroundColor], or if that is not
+ * a color from the theme, this will keep the same content color set above the bottom sheet.
+ * @param scrimColor The color of the scrim that is applied to the rest of the screen when the
+ * bottom sheet is visible. If the color passed is [Color.Unspecified], then a scrim will no
+ * longer be applied and the bottom sheet will not block interaction with the rest of the screen
+ * when visible.
+ * @param content The content of rest of the screen.
+ */
+@Composable
+fun ModalBottomSheetLayout(
+    sheetContent: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    sheetState: ModalBottomSheetState =
+        rememberModalBottomSheetState(Hidden),
+    sheetShape: Shape = MaterialTheme.shapes.large,
+    sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
+    sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+    scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
+    content: @Composable () -> Unit
+) {
+    val scope = rememberCoroutineScope()
+    BoxWithConstraints(modifier) {
+        val fullHeight = constraints.maxHeight.toFloat()
+        val sheetHeightState = remember { mutableStateOf<Float?>(null) }
+        Box(Modifier.fillMaxSize()) {
+            content()
+            Scrim(
+                color = scrimColor,
+                onDismiss = {
+                    if (sheetState.confirmStateChange(Hidden)) {
+                        scope.launch { sheetState.hide() }
+                    }
+                },
+                visible = sheetState.targetValue != Hidden
+            )
+        }
+        Surface(
+            Modifier
+                .fillMaxWidth()
+                .nestedScroll(sheetState.nestedScrollConnection)
+                .offset {
+                    val y = if (sheetState.anchors.isEmpty()) {
+                        // if we don't know our anchors yet, render the sheet as hidden
+                        fullHeight.roundToInt()
+                    } else {
+                        // if we do know our anchors, respect them
+                        sheetState.offset.value.roundToInt()
+                    }
+                    IntOffset(0, y)
+                }
+                .bottomSheetSwipeable(sheetState, fullHeight, sheetHeightState)
+                .onGloballyPositioned {
+                    sheetHeightState.value = it.size.height.toFloat()
+                }
+                .semantics {
+                    if (sheetState.isVisible) {
+                        dismiss {
+                            if (sheetState.confirmStateChange(Hidden)) {
+                                scope.launch { sheetState.hide() }
+                            }
+                            true
+                        }
+                        if (sheetState.currentValue == HalfExpanded) {
+                            expand {
+                                if (sheetState.confirmStateChange(Expanded)) {
+                                    scope.launch { sheetState.expand() }
+                                }
+                                true
+                            }
+                        } else if (sheetState.hasHalfExpandedState) {
+                            collapse {
+                                if (sheetState.confirmStateChange(HalfExpanded)) {
+                                    scope.launch { sheetState.halfExpand() }
+                                }
+                                true
+                            }
+                        }
+                    }
+                },
+            shape = sheetShape,
+            shadowElevation = sheetElevation,
+            color = sheetBackgroundColor,
+            contentColor = sheetContentColor
+        ) {
+            Column(content = sheetContent)
+        }
+    }
+}
+
+@Suppress("ModifierInspectorInfo")
+private fun Modifier.bottomSheetSwipeable(
+    sheetState: ModalBottomSheetState,
+    fullHeight: Float,
+    sheetHeightState: State<Float?>
+): Modifier {
+    val sheetHeight = sheetHeightState.value
+    val modifier = if (sheetHeight != null) {
+        val anchors = if (sheetHeight < fullHeight / 2 || sheetState.isSkipHalfExpanded) {
+            mapOf(
+                fullHeight to Hidden,
+                fullHeight - sheetHeight to Expanded
+            )
+        } else {
+            mapOf(
+                fullHeight to Hidden,
+                fullHeight / 2 to HalfExpanded,
+                max(0f, fullHeight - sheetHeight) to Expanded
+            )
+        }
+        Modifier.swipeable(
+            state = sheetState,
+            anchors = anchors,
+            orientation = Orientation.Vertical,
+            enabled = sheetState.currentValue != Hidden,
+            resistance = null
+        )
+    } else {
+        Modifier
+    }
+
+    return this.then(modifier)
+}
+
+@Composable
+private fun Scrim(
+    color: Color,
+    onDismiss: () -> Unit,
+    visible: Boolean
+) {
+    if (color.isSpecified) {
+        val alpha by animateFloatAsState(
+            targetValue = if (visible) 1f else 0f,
+            animationSpec = TweenSpec()
+        )
+        LocalConfiguration.current
+        val resources = LocalContext.current.resources
+        val closeSheet = resources.getString(R.string.close_sheet)
+        val dismissModifier = if (visible) {
+            Modifier
+                .pointerInput(onDismiss) { detectTapGestures { onDismiss() } }
+                .semantics(mergeDescendants = true) {
+                    contentDescription = closeSheet
+                    onClick { onDismiss(); true }
+                }
+        } else {
+            Modifier
+        }
+
+        Canvas(
+            Modifier
+                .fillMaxSize()
+                .then(dismissModifier)
+        ) {
+            drawRect(color = color, alpha = alpha)
+        }
+    }
+}
+
+/**
+ * Contains useful Defaults for [ModalBottomSheetLayout].
+ */
+object ModalBottomSheetDefaults {
+
+    /**
+     * The default elevation used by [ModalBottomSheetLayout].
+     */
+    val Elevation = 16.dp
+
+    /**
+     * The default scrim color used by [ModalBottomSheetLayout].
+     */
+    val scrimColor: Color
+        @Composable
+        get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
new file mode 100644
index 0000000..3e2de83
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
@@ -0,0 +1,875 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.material
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import com.android.credentialmanager.common.material.SwipeableDefaults.AnimationSpec
+import com.android.credentialmanager.common.material.SwipeableDefaults.StandardResistanceFactor
+import com.android.credentialmanager.common.material.SwipeableDefaults.VelocityThreshold
+import com.android.credentialmanager.common.material.SwipeableDefaults.resistanceConfig
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods
+ * to change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Stable
+open class SwipeableState<T>(
+    initialValue: T,
+    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+    /**
+     * The current value of the state.
+     *
+     * If no swipe or animation is in progress, this corresponds to the anchor at which the
+     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+     */
+    var currentValue: T by mutableStateOf(initialValue)
+        private set
+
+    /**
+     * Whether the state is currently animating.
+     */
+    var isAnimationRunning: Boolean by mutableStateOf(false)
+        private set
+
+    /**
+     * The current position (in pixels) of the [swipeable].
+     *
+     * You should use this state to offset your content accordingly. The recommended way is to
+     * use `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+     */
+    val offset: State<Float> get() = offsetState
+
+    /**
+     * The amount by which the [swipeable] has been swiped past its bounds.
+     */
+    val overflow: State<Float> get() = overflowState
+
+    // Use `Float.NaN` as a placeholder while the state is uninitialised.
+    private val offsetState = mutableStateOf(0f)
+    private val overflowState = mutableStateOf(0f)
+
+    // the source of truth for the "real"(non ui) position
+    // basically position in bounds + overflow
+    private val absoluteOffset = mutableStateOf(0f)
+
+    // current animation target, if animating, otherwise null
+    private val animationTarget = mutableStateOf<Float?>(null)
+
+    internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+        snapshotFlow { anchors }
+            .filter { it.isNotEmpty() }
+            .take(1)
+
+    internal var minBound = Float.NEGATIVE_INFINITY
+    internal var maxBound = Float.POSITIVE_INFINITY
+
+    internal fun ensureInit(newAnchors: Map<Float, T>) {
+        if (anchors.isEmpty()) {
+            // need to do initial synchronization synchronously :(
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) {
+                "The initial value must have an associated anchor."
+            }
+            offsetState.value = initialOffset
+            absoluteOffset.value = initialOffset
+        }
+    }
+
+    internal suspend fun processNewAnchors(
+        oldAnchors: Map<Float, T>,
+        newAnchors: Map<Float, T>
+    ) {
+        if (oldAnchors.isEmpty()) {
+            // If this is the first time that we receive anchors, then we need to initialise
+            // the state so we snap to the offset associated to the initial value.
+            minBound = newAnchors.keys.minOrNull()!!
+            maxBound = newAnchors.keys.maxOrNull()!!
+            val initialOffset = newAnchors.getOffset(currentValue)
+            requireNotNull(initialOffset) {
+                "The initial value must have an associated anchor."
+            }
+            snapInternalToOffset(initialOffset)
+        } else if (newAnchors != oldAnchors) {
+            // If we have received new anchors, then the offset of the current value might
+            // have changed, so we need to animate to the new offset. If the current value
+            // has been removed from the anchors then we animate to the closest anchor
+            // instead. Note that this stops any ongoing animation.
+            minBound = Float.NEGATIVE_INFINITY
+            maxBound = Float.POSITIVE_INFINITY
+            val animationTargetValue = animationTarget.value
+            // if we're in the animation already, let's find it a new home
+            val targetOffset = if (animationTargetValue != null) {
+                // first, try to map old state to the new state
+                val oldState = oldAnchors[animationTargetValue]
+                val newState = newAnchors.getOffset(oldState)
+                // return new state if exists, or find the closes one among new anchors
+                newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+            } else {
+                // we're not animating, proceed by finding the new anchors for an old value
+                val actualOldValue = oldAnchors[offset.value]
+                val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+                newAnchors.getOffset(value) ?: newAnchors
+                    .keys.minByOrNull { abs(it - offset.value) }!!
+            }
+            try {
+                animateInternalToOffset(targetOffset, animationSpec)
+            } catch (c: CancellationException) {
+                // If the animation was interrupted for any reason, snap as a last resort.
+                snapInternalToOffset(targetOffset)
+            } finally {
+                currentValue = newAnchors.getValue(targetOffset)
+                minBound = newAnchors.keys.minOrNull()!!
+                maxBound = newAnchors.keys.maxOrNull()!!
+            }
+        }
+    }
+
+    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+    internal var velocityThreshold by mutableStateOf(0f)
+
+    internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+    internal val draggableState = DraggableState {
+        val newAbsolute = absoluteOffset.value + it
+        val clamped = newAbsolute.coerceIn(minBound, maxBound)
+        val overflow = newAbsolute - clamped
+        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+        offsetState.value = clamped + resistanceDelta
+        overflowState.value = overflow
+        absoluteOffset.value = newAbsolute
+    }
+
+    private suspend fun snapInternalToOffset(target: Float) {
+        draggableState.drag {
+            dragBy(target - absoluteOffset.value)
+        }
+    }
+
+    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+        draggableState.drag {
+            var prevValue = absoluteOffset.value
+            animationTarget.value = target
+            isAnimationRunning = true
+            try {
+                Animatable(prevValue).animateTo(target, spec) {
+                    dragBy(this.value - prevValue)
+                    prevValue = this.value
+                }
+            } finally {
+                animationTarget.value = null
+                isAnimationRunning = false
+            }
+        }
+    }
+
+    /**
+     * The target value of the state.
+     *
+     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+     * swipe finished. If an animation is running, this is the target value of that animation.
+     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+     */
+    val targetValue: T
+        get() {
+            // TODO(calintat): Track current velocity (b/149549482) and use that here.
+            val target = animationTarget.value ?: computeTarget(
+                offset = offset.value,
+                lastValue = anchors.getOffset(currentValue) ?: offset.value,
+                anchors = anchors.keys,
+                thresholds = thresholds,
+                velocity = 0f,
+                velocityThreshold = Float.POSITIVE_INFINITY
+            )
+            return anchors[target] ?: currentValue
+        }
+
+    /**
+     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+     *
+     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+     */
+    val progress: SwipeProgress<T>
+        get() {
+            val bounds = findBounds(offset.value, anchors.keys)
+            val from: T
+            val to: T
+            val fraction: Float
+            when (bounds.size) {
+                0 -> {
+                    from = currentValue
+                    to = currentValue
+                    fraction = 1f
+                }
+                1 -> {
+                    from = anchors.getValue(bounds[0])
+                    to = anchors.getValue(bounds[0])
+                    fraction = 1f
+                }
+                else -> {
+                    val (a, b) =
+                        if (direction > 0f) {
+                            bounds[0] to bounds[1]
+                        } else {
+                            bounds[1] to bounds[0]
+                        }
+                    from = anchors.getValue(a)
+                    to = anchors.getValue(b)
+                    fraction = (offset.value - a) / (b - a)
+                }
+            }
+            return SwipeProgress(from, to, fraction)
+        }
+
+    /**
+     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+     *
+     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+     */
+    val direction: Float
+        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+    /**
+     * Set the state without any animation and suspend until it's set
+     *
+     * @param targetValue The new target value to set [currentValue] to.
+     */
+    suspend fun snapTo(targetValue: T) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val targetOffset = anchors.getOffset(targetValue)
+            requireNotNull(targetOffset) {
+                "The target value must have an associated anchor."
+            }
+            snapInternalToOffset(targetOffset)
+            currentValue = targetValue
+        }
+    }
+
+    /**
+     * Set the state to the target value by starting an animation.
+     *
+     * @param targetValue The new value to animate to.
+     * @param anim The animation that will be used to animate to the new value.
+     */
+    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            try {
+                val targetOffset = anchors.getOffset(targetValue)
+                requireNotNull(targetOffset) {
+                    "The target value must have an associated anchor."
+                }
+                animateInternalToOffset(targetOffset, anim)
+            } finally {
+                val endOffset = absoluteOffset.value
+                val endValue = anchors
+                    // fighting rounding error once again, anchor should be as close as 0.5 pixels
+                    .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+                    .values.firstOrNull() ?: currentValue
+                currentValue = endValue
+            }
+        }
+    }
+
+    /**
+     * Perform fling with settling to one of the anchors which is determined by the given
+     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+     * since it will settle at the anchor.
+     *
+     * In general cases, [swipeable] flings by itself when being swiped. This method is to be
+     * used for nested scroll logic that wraps the [swipeable]. In nested scroll developer may
+     * want to trigger settling fling when the child scroll container reaches the bound.
+     *
+     * @param velocity velocity to fling and settle with
+     *
+     * @return the reason fling ended
+     */
+    suspend fun performFling(velocity: Float) {
+        latestNonEmptyAnchorsFlow.collect { anchors ->
+            val lastAnchor = anchors.getOffset(currentValue)!!
+            val targetValue = computeTarget(
+                offset = offset.value,
+                lastValue = lastAnchor,
+                anchors = anchors.keys,
+                thresholds = thresholds,
+                velocity = velocity,
+                velocityThreshold = velocityThreshold
+            )
+            val targetState = anchors[targetValue]
+            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+            // If the user vetoed the state change, rollback to the previous state.
+            else animateInternalToOffset(lastAnchor, animationSpec)
+        }
+    }
+
+    /**
+     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+     * gesture flow.
+     *
+     * Note: This method performs generic drag and it won't settle to any particular anchor, *
+     * leaving swipeable in between anchors. When done dragging, [performFling] must be
+     * called as well to ensure swipeable will settle at the anchor.
+     *
+     * In general cases, [swipeable] drags by itself when being swiped. This method is to be
+     * used for nested scroll logic that wraps the [swipeable]. In nested scroll developer may
+     * want to force drag when the child scroll container reaches the bound.
+     *
+     * @param delta delta in pixels to drag by
+     *
+     * @return the amount of [delta] consumed
+     */
+    fun performDrag(delta: Float): Float {
+        val potentiallyConsumed = absoluteOffset.value + delta
+        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+        val deltaToConsume = clamped - absoluteOffset.value
+        if (abs(deltaToConsume) > 0) {
+            draggableState.dispatchRawDelta(deltaToConsume)
+        }
+        return deltaToConsume
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [SwipeableState].
+         */
+        fun <T : Any> Saver(
+            animationSpec: AnimationSpec<Float>,
+            confirmStateChange: (T) -> Boolean
+        ) = Saver<SwipeableState<T>, T>(
+            save = { it.currentValue },
+            restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+        )
+    }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to].
+ * Must be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+    val from: T,
+    val to: T,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    val fraction: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is SwipeProgress<*>) return false
+
+        if (from != other.from) return false
+        if (to != other.to) return false
+        if (fraction != other.fraction) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = from?.hashCode() ?: 0
+        result = 31 * result + (to?.hashCode() ?: 0)
+        result = 31 * result + fraction.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+    initialValue: T,
+    animationSpec: AnimationSpec<Float> = AnimationSpec,
+    confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+    return rememberSaveable(
+        saver = SwipeableState.Saver(
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    ) {
+        SwipeableState(
+            initialValue = initialValue,
+            animationSpec = animationSpec,
+            confirmStateChange = confirmStateChange
+        )
+    }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ *  1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ *  2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ *  [value] will be notified to update their state to the new value of the [SwipeableState] by
+ *  invoking [onValueChange]. If the owner does not update their state to the provided value for
+ *  some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+    value: T,
+    onValueChange: (T) -> Unit,
+    animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+    val swipeableState = remember {
+        SwipeableState(
+            initialValue = value,
+            animationSpec = animationSpec,
+            confirmStateChange = { true }
+        )
+    }
+    val forceAnimationCheck = remember { mutableStateOf(false) }
+    LaunchedEffect(value, forceAnimationCheck.value) {
+        if (value != swipeableState.currentValue) {
+            swipeableState.animateTo(value)
+        }
+    }
+    DisposableEffect(swipeableState.currentValue) {
+        if (value != swipeableState.currentValue) {
+            onValueChange(swipeableState.currentValue)
+            forceAnimationCheck.value = !forceAnimationCheck.value
+        }
+        onDispose { }
+    }
+    return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]).
+ * Note that this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to
+ * the new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @sample androidx.compose.material.samples.SwipeableSample
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ * used to determine which state to animate to when swiping stops. This is represented as a lambda
+ * that takes two states and returns the threshold between them in the form of a [ThresholdConfig].
+ * Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom
+ * swipe will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to
+ * the internal [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed
+ * in order to animate to the next state, even if the positional [thresholds] have not been reached.
+ */
+fun <T> Modifier.swipeable(
+    state: SwipeableState<T>,
+    anchors: Map<Float, T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+    velocityThreshold: Dp = VelocityThreshold
+) = composed(
+    inspectorInfo = debugInspectorInfo {
+        name = "swipeable"
+        properties["state"] = state
+        properties["anchors"] = anchors
+        properties["orientation"] = orientation
+        properties["enabled"] = enabled
+        properties["reverseDirection"] = reverseDirection
+        properties["interactionSource"] = interactionSource
+        properties["thresholds"] = thresholds
+        properties["resistance"] = resistance
+        properties["velocityThreshold"] = velocityThreshold
+    }
+) {
+    require(anchors.isNotEmpty()) {
+        "You must have at least one anchor."
+    }
+    require(anchors.values.distinct().count() == anchors.size) {
+        "You cannot have two anchors mapped to the same state."
+    }
+    val density = LocalDensity.current
+    state.ensureInit(anchors)
+    LaunchedEffect(anchors, state) {
+        val oldAnchors = state.anchors
+        state.anchors = anchors
+        state.resistance = resistance
+        state.thresholds = { a, b ->
+            val from = anchors.getValue(a)
+            val to = anchors.getValue(b)
+            with(thresholds(from, to)) { density.computeThreshold(a, b) }
+        }
+        with(density) {
+            state.velocityThreshold = velocityThreshold.toPx()
+        }
+        state.processNewAnchors(oldAnchors, anchors)
+    }
+
+    Modifier.draggable(
+        orientation = orientation,
+        enabled = enabled,
+        reverseDirection = reverseDirection,
+        interactionSource = interactionSource,
+        startDragImmediately = state.isAnimationRunning,
+        onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+        state = state.draggableState
+    )
+}
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+    /**
+     * Compute the value of the threshold (in pixels), once the values of the anchors are known.
+     */
+    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return fromValue + offset.toPx() * sign(toValue - fromValue)
+    }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    private val fraction: Float
+) : ThresholdConfig {
+    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+        return lerp(fromValue, toValue, fraction)
+    }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines
+ * the amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied
+ * to, or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user
+ * has run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe
+ * this right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound.
+ * Must not be negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound.
+ * Must not be negative.
+ */
+@Immutable
+class ResistanceConfig(
+    /*@FloatRange(from = 0.0, fromInclusive = false)*/
+    val basis: Float,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMin: Float = StandardResistanceFactor,
+    /*@FloatRange(from = 0.0)*/
+    val factorAtMax: Float = StandardResistanceFactor
+) {
+    fun computeResistance(overflow: Float): Float {
+        val factor = if (overflow < 0) factorAtMin else factorAtMax
+        if (factor == 0f) return 0f
+        val progress = (overflow / basis).coerceIn(-1f, 1f)
+        return basis / factor * sin(progress * PI.toFloat() / 2)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ResistanceConfig) return false
+
+        if (basis != other.basis) return false
+        if (factorAtMin != other.factorAtMin) return false
+        if (factorAtMax != other.factorAtMax) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = basis.hashCode()
+        result = 31 * result + factorAtMin.hashCode()
+        result = 31 * result + factorAtMax.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+    }
+}
+
+/**
+ *  Given an offset x and a set of anchors, return a list of anchors:
+ *   1. [ ] if the set of anchors is empty,
+ *   2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x'
+ *      is x rounded to the exact value of the matching anchor,
+ *   3. [ min ] if min is the minimum anchor and x < min,
+ *   4. [ max ] if max is the maximum anchor and x > max, or
+ *   5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(
+    offset: Float,
+    anchors: Set<Float>
+): List<Float> {
+    // Find the anchors the target lies between with a little bit of rounding error.
+    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+    return when {
+        a == null ->
+            // case 1 or 3
+            listOfNotNull(b)
+        b == null ->
+            // case 4
+            listOf(a)
+        a == b ->
+            // case 2
+            // Can't return offset itself here since it might not be exactly equal
+            // to the anchor, despite being considered an exact match.
+            listOf(a)
+        else ->
+            // case 5
+            listOf(a, b)
+    }
+}
+
+private fun computeTarget(
+    offset: Float,
+    lastValue: Float,
+    anchors: Set<Float>,
+    thresholds: (Float, Float) -> Float,
+    velocity: Float,
+    velocityThreshold: Float
+): Float {
+    val bounds = findBounds(offset, anchors)
+    return when (bounds.size) {
+        0 -> lastValue
+        1 -> bounds[0]
+        else -> {
+            val lower = bounds[0]
+            val upper = bounds[1]
+            if (lastValue <= offset) {
+                // Swiping from lower to upper (positive).
+                if (velocity >= velocityThreshold) {
+                    return upper
+                } else {
+                    val threshold = thresholds(lower, upper)
+                    if (offset < threshold) lower else upper
+                }
+            } else {
+                // Swiping from upper to lower (negative).
+                if (velocity <= -velocityThreshold) {
+                    return lower
+                } else {
+                    val threshold = thresholds(upper, lower)
+                    if (offset > threshold) upper else lower
+                }
+            }
+        }
+    }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+    return entries.firstOrNull { it.value == state }?.key
+}
+
+/**
+ * Contains useful defaults for [swipeable] and [SwipeableState].
+ */
+object SwipeableDefaults {
+    /**
+     * The default animation used by [SwipeableState].
+     */
+    val AnimationSpec = SpringSpec<Float>()
+
+    /**
+     * The default velocity threshold (1.8 dp per millisecond) used by [swipeable].
+     */
+    val VelocityThreshold = 125.dp
+
+    /**
+     * A stiff resistance factor which indicates that swiping isn't available right now.
+     */
+    const val StiffResistanceFactor = 20f
+
+    /**
+     * A standard resistance factor which indicates that the user has run out of things to see.
+     */
+    const val StandardResistanceFactor = 10f
+
+    /**
+     * The default resistance config used by [swipeable].
+     *
+     * This returns `null` if there is one anchor. If there are at least two anchors, it returns
+     * a [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+     */
+    fun resistanceConfig(
+        anchors: Set<Float>,
+        factorAtMin: Float = StandardResistanceFactor,
+        factorAtMax: Float = StandardResistanceFactor
+    ): ResistanceConfig? {
+        return if (anchors.size <= 1) {
+            null
+        } else {
+            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+            ResistanceConfig(basis, factorAtMin, factorAtMax)
+        }
+    }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+    get() = object : NestedScrollConnection {
+        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+            val delta = available.toFloat()
+            return if (delta < 0 && source == NestedScrollSource.Drag) {
+                performDrag(delta).toOffset()
+            } else {
+                Offset.Zero
+            }
+        }
+
+        override fun onPostScroll(
+            consumed: Offset,
+            available: Offset,
+            source: NestedScrollSource
+        ): Offset {
+            return if (source == NestedScrollSource.Drag) {
+                performDrag(available.toFloat()).toOffset()
+            } else {
+                Offset.Zero
+            }
+        }
+
+        override suspend fun onPreFling(available: Velocity): Velocity {
+            val toFling = Offset(available.x, available.y).toFloat()
+            return if (toFling < 0 && offset.value > minBound) {
+                performFling(velocity = toFling)
+                // since we go to the anchor with tween settling, consume all for the best UX
+                available
+            } else {
+                Velocity.Zero
+            }
+        }
+
+        override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+            performFling(velocity = Offset(available.x, available.y).toFloat())
+            return available
+        }
+
+        private fun Float.toOffset(): Offset = Offset(0f, this)
+
+        private fun Offset.toFloat(): Float = this.y
+    }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
similarity index 66%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
index f79ca10..177d0e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package com.android.credentialmanager.common.ui
 
-import android.content.Intent;
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
 
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
-}
+@Composable
+fun CancelButton(text: String, onClick: () -> Unit) {
+    TextButton(onClick = onClick) {
+        Text(text = text)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
similarity index 65%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
index f79ca10..b2b0bdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package com.android.credentialmanager.common.ui
 
-import android.content.Intent;
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
-}
+@Composable
+fun ConfirmButton(text: String, onClick: () -> Unit) {
+    FilledTonalButton(onClick = onClick) {
+        Text(text = text)
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index db0f337e..70bb867 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,20 +22,27 @@
   val icon: Drawable,
   val name: String,
   val displayName: String,
-  val credentialTypeIcon: Drawable,
-  val createOptions: List<CreateOptionInfo>,
+  var createOptions: List<CreateOptionInfo>,
   val isDefault: Boolean,
 )
 
-data class CreateOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+open class EntryInfo (
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
 )
 
+class CreateOptionInfo(
+  entryKey: String,
+  entrySubkey: String,
+  val userProviderDisplayName: String,
+  val credentialTypeIcon: Drawable,
+  val profileIcon: Drawable,
+  val passwordCount: Int,
+  val passkeyCount: Int,
+  val totalCredentialCount: Int,
+  val lastUsedTimeMillis: Long?,
+) : EntryInfo(entryKey, entrySubkey)
+
 data class RequestDisplayInfo(
   val userName: String,
   val displayName: String,
@@ -49,7 +56,7 @@
  */
 data class ActiveEntry (
   val activeProvider: ProviderInfo,
-  val activeCreateOptionInfo: CreateOptionInfo,
+  val activeEntryInfo: EntryInfo,
 )
 
 /** 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 aeea46a..2344847 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -1,6 +1,6 @@
 package com.android.credentialmanager.createflow
 
-import androidx.compose.foundation.BorderStroke
+import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
@@ -9,24 +9,18 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonColors
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Card
-import androidx.compose.material.Chip
-import androidx.compose.material.ChipDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.Text
-import androidx.compose.material.TextButton
-import androidx.compose.material.TopAppBar
+import androidx.compose.material3.Card
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
@@ -39,16 +33,14 @@
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.R
-import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
-import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
-import com.android.credentialmanager.ui.theme.Grey100
-import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.Typography
-import com.android.credentialmanager.ui.theme.lightBackgroundColor
-import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
-import com.android.credentialmanager.ui.theme.lightSurface1
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
+import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CreatePasskeyScreen(
   viewModel: CreatePasskeyViewModel,
@@ -74,7 +66,7 @@
         CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
           requestDisplayInfo = uiState.requestDisplayInfo,
           providerInfo = uiState.activeEntry?.activeProvider!!,
-          createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
+          createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
           onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
           onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
           onCancel = viewModel::onCancel,
@@ -92,8 +84,8 @@
         )
       }
     },
-    scrimColor = Color.Transparent,
-    sheetShape = Shapes.medium,
+    scrimColor = MaterialTheme.colorScheme.scrim,
+    sheetShape = MaterialTheme.shapes.medium,
   ) {}
   LaunchedEffect(state.currentValue) {
     if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -107,9 +99,7 @@
   onConfirm: () -> Unit,
   onCancel: () -> Unit,
 ) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
+  Card() {
     Column() {
       Icon(
         painter = painterResource(R.drawable.ic_passkey),
@@ -119,7 +109,7 @@
       )
       Text(
         text = stringResource(R.string.passkey_creation_intro_title),
-        style = Typography.subtitle1,
+        style = MaterialTheme.typography.titleMedium,
         modifier = Modifier
           .padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally)
@@ -130,7 +120,7 @@
       )
       Text(
         text = stringResource(R.string.passkey_creation_intro_body),
-        style = Typography.body1,
+        style = MaterialTheme.typography.bodyLarge,
         modifier = Modifier.padding(horizontal = 28.dp)
       )
       Divider(
@@ -143,11 +133,11 @@
       ) {
         CancelButton(
           stringResource(R.string.string_cancel),
-          onclick = onCancel
+          onClick = onCancel
         )
         ConfirmButton(
           stringResource(R.string.string_continue),
-          onclick = onConfirm
+          onClick = onConfirm
         )
       }
       Divider(
@@ -159,25 +149,23 @@
   }
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ProviderSelectionCard(
   providerList: List<ProviderInfo>,
   onProviderSelected: (String) -> Unit,
   onCancel: () -> Unit
 ) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
+  Card() {
     Column() {
       Text(
         text = stringResource(R.string.choose_provider_title),
-        style = Typography.subtitle1,
+        style = MaterialTheme.typography.titleMedium,
         modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
       Text(
         text = stringResource(R.string.choose_provider_body),
-        style = Typography.body1,
+        style = MaterialTheme.typography.bodyLarge,
         modifier = Modifier.padding(horizontal = 28.dp)
       )
       Divider(
@@ -185,10 +173,10 @@
         color = Color.Transparent
       )
       Card(
-        shape = Shapes.medium,
+        shape = MaterialTheme.shapes.large,
         modifier = Modifier
           .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
+          .align(alignment = Alignment.CenterHorizontally),
       ) {
         LazyColumn(
           verticalArrangement = Arrangement.spacedBy(2.dp)
@@ -219,28 +207,27 @@
   }
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsSelectionCard(
   providerList: List<ProviderInfo>,
   onBackButtonSelected: () -> Unit,
   onOptionSelected: (ActiveEntry) -> Unit
 ) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
+  Card() {
     Column() {
       TopAppBar(
         title = {
-          Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1)
+          Text(
+            text = stringResource(R.string.string_more_options),
+            style = MaterialTheme.typography.titleMedium
+          )
         },
-        backgroundColor = lightBackgroundColor,
-        elevation = 0.dp,
-        navigationIcon =
-        {
+        navigationIcon = {
           IconButton(onClick = onBackButtonSelected) {
-            Icon(Icons.Filled.ArrowBack, "backIcon"
-            )
+            Icon(
+              Icons.Filled.ArrowBack,
+              stringResource(R.string.accessibility_back_arrow_button))
           }
         }
       )
@@ -250,12 +237,12 @@
       )
       Text(
         text = stringResource(R.string.create_passkey_at),
-        style = Typography.body1,
+        style = MaterialTheme.typography.bodyLarge,
         modifier = Modifier.padding(horizontal = 28.dp),
         textAlign = TextAlign.Center
       )
       Card(
-        shape = Shapes.medium,
+        shape = MaterialTheme.shapes.large,
         modifier = Modifier
           .padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally)
@@ -287,19 +274,17 @@
   }
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsRowIntroCard(
   providerInfo: ProviderInfo,
   onDefaultOrNotSelected: () -> Unit,
 ) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
+  Card() {
     Column() {
       Text(
         text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
-        style = Typography.subtitle1,
+        style = MaterialTheme.typography.titleMedium,
         modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
       Row(
@@ -308,11 +293,11 @@
       ) {
         CancelButton(
           stringResource(R.string.use_once),
-          onclick = onDefaultOrNotSelected
+          onClick = onDefaultOrNotSelected
         )
         ConfirmButton(
           stringResource(R.string.set_as_default),
-          onclick = onDefaultOrNotSelected
+          onClick = onDefaultOrNotSelected
         )
       }
       Divider(
@@ -324,74 +309,31 @@
   }
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
-  Chip(
+  SuggestionChip(
     modifier = Modifier.fillMaxWidth(),
     onClick = {onProviderSelected(providerInfo.name)},
-    leadingIcon = {
+    icon = {
       Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
             bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
             // painter = painterResource(R.drawable.ic_passkey),
             // TODO: add description.
             contentDescription = "")
     },
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Text(
-      text = providerInfo.displayName,
-      style = Typography.button,
-      modifier = Modifier.padding(vertical = 18.dp)
-    )
-  }
-}
-
-@Composable
-fun CancelButton(text: String, onclick: () -> Unit) {
-  val colors = ButtonDefaults.buttonColors(
-    backgroundColor = lightBackgroundColor
+    shape = MaterialTheme.shapes.large,
+    label = {
+      Text(
+        text = providerInfo.displayName,
+        style = MaterialTheme.typography.labelLarge,
+        modifier = Modifier.padding(vertical = 18.dp)
+      )
+    }
   )
-  NavigationButton(
-    border = BorderStroke(1.dp, lightSurface1),
-    colors = colors,
-    text = text,
-    onclick = onclick)
 }
 
-@Composable
-fun ConfirmButton(text: String, onclick: () -> Unit) {
-  val colors = ButtonDefaults.buttonColors(
-    backgroundColor = lightColorAccentSecondary
-  )
-  NavigationButton(
-    colors = colors,
-    text = text,
-    onclick = onclick)
-}
-
-@Composable
-fun NavigationButton(
-    border: BorderStroke? = null,
-    colors: ButtonColors,
-    text: String,
-    onclick: () -> Unit
-) {
-  Button(
-    onClick = onclick,
-    shape = Shapes.small,
-    colors = colors,
-    border = border
-  ) {
-    Text(text = text, style = Typography.button)
-  }
-}
-
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CreationSelectionCard(
   requestDisplayInfo: RequestDisplayInfo,
@@ -403,12 +345,10 @@
   multiProvider: Boolean,
   onMoreOptionsSelected: () -> Unit,
 ) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
+  Card() {
     Column() {
       Icon(
-        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
         contentDescription = null,
         tint = Color.Unspecified,
         modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -422,41 +362,45 @@
           else -> stringResource(R.string.choose_create_option_sign_in_title,
             providerInfo.displayName)
         },
-        style = Typography.subtitle1,
+        style = MaterialTheme.typography.titleMedium,
         modifier = Modifier.padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally),
         textAlign = TextAlign.Center,
       )
       Text(
         text = requestDisplayInfo.appDomainName,
-        style = Typography.body2,
+        style = MaterialTheme.typography.bodyMedium,
         modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
       )
       Text(
         text = stringResource(
           R.string.choose_create_option_description,
           when (requestDisplayInfo.type) {
-            TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
-            TYPE_PASSWORD_CREDENTIAL -> "passwords"
-            else -> "sign-ins"
+            TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
+            TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
+            else -> stringResource(R.string.sign_ins)
           },
           providerInfo.displayName,
-          createOptionInfo.title),
-        style = Typography.body1,
+          createOptionInfo.userProviderDisplayName
+        ),
+        style = MaterialTheme.typography.bodyLarge,
         modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
       Card(
-        shape = Shapes.medium,
+        shape = MaterialTheme.shapes.large,
         modifier = Modifier
           .padding(horizontal = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
+          .align(alignment = Alignment.CenterHorizontally),
       ) {
         LazyColumn(
           verticalArrangement = Arrangement.spacedBy(2.dp)
         ) {
             item {
-              PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
-                onOptionSelected = onOptionSelected)
+              PrimaryCreateOptionRow(
+                requestDisplayInfo = requestDisplayInfo,
+                createOptionInfo = createOptionInfo,
+                onOptionSelected = onOptionSelected
+              )
             }
         }
       }
@@ -486,11 +430,11 @@
       ) {
         CancelButton(
           stringResource(R.string.string_cancel),
-          onclick = onCancel
+          onClick = onCancel
         )
         ConfirmButton(
           stringResource(R.string.string_continue),
-          onclick = onConfirm
+          onClick = onConfirm
         )
       }
       Divider(
@@ -502,76 +446,76 @@
   }
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun PrimaryCreateOptionRow(
   requestDisplayInfo: RequestDisplayInfo,
+  createOptionInfo: CreateOptionInfo,
   onOptionSelected: () -> Unit
 ) {
-  Chip(
+  SuggestionChip(
     modifier = Modifier.fillMaxWidth(),
     onClick = onOptionSelected,
-    // TODO: Add an icon generated by provider according to requestDisplayInfo type
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Column() {
-      Text(
-        text = requestDisplayInfo.userName,
-        style = Typography.h6,
-        modifier = Modifier.padding(top = 16.dp)
-      )
-      Text(
-        text = requestDisplayInfo.displayName,
-        style = Typography.body2,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
+    icon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+    },
+    shape = MaterialTheme.shapes.large,
+    label = {
+      Column() {
+        Text(
+          text = requestDisplayInfo.userName,
+          style = MaterialTheme.typography.titleLarge,
+          modifier = Modifier.padding(top = 16.dp)
+        )
+        Text(
+          text = requestDisplayInfo.displayName,
+          style = MaterialTheme.typography.bodyMedium,
+          modifier = Modifier.padding(bottom = 16.dp)
+        )
+      }
     }
-  }
+  )
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsInfoRow(
   providerInfo: ProviderInfo,
   createOptionInfo: CreateOptionInfo,
   onOptionSelected: () -> Unit
 ) {
-    Chip(
+  SuggestionChip(
         modifier = Modifier.fillMaxWidth(),
         onClick = onOptionSelected,
-        leadingIcon = {
+        icon = {
             Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-                bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+                bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
                 // painter = painterResource(R.drawable.ic_passkey),
                 // TODO: add description.
                 contentDescription = "")
         },
-        colors = ChipDefaults.chipColors(
-            backgroundColor = Grey100,
-            leadingIconContentColor = Grey100
-        ),
-        shape = Shapes.large
-    ) {
-        Column() {
+        shape = MaterialTheme.shapes.large,
+        label = {
+          Column() {
             Text(
                 text =
                 if (providerInfo.createOptions.size > 1)
                 {stringResource(R.string.more_options_title_multiple_options,
-                  providerInfo.displayName, createOptionInfo.title)} else {
+                  providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
                   stringResource(R.string.more_options_title_one_option,
                     providerInfo.displayName)},
-                style = Typography.h6,
+                style = MaterialTheme.typography.titleLarge,
                 modifier = Modifier.padding(top = 16.dp)
             )
             Text(
-                text = createOptionInfo.usageData,
-                style = Typography.body2,
+                text = stringResource(R.string.more_options_usage_data,
+                  createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
+                style = MaterialTheme.typography.bodyMedium,
                 modifier = Modifier.padding(bottom = 16.dp)
             )
+          }
         }
-    }
+    )
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 615da4e..2e9758a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -73,21 +73,6 @@
     )
   }
 
-  fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
-    Log.d(
-      "Account Selector",
-      "Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
-    )
-    CredentialManagerRepo.getInstance().onOptionSelected(
-      uiState.activeEntry?.activeProvider!!.name,
-      entryKey,
-      entrySubkey
-    )
-    dialogResult.value = DialogResult(
-      ResultState.COMPLETE,
-    )
-  }
-
   fun getProviderInfoByName(providerName: String): ProviderInfo {
     return uiState.providers.single {
       it.name.equals(providerName)
@@ -126,18 +111,18 @@
   }
 
   fun onPrimaryCreateOptionInfoSelected() {
-    var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
-    var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
+    val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
+    val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
     Log.d(
       "Account Selector",
       "Option selected for creation: " +
-              "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
+              "{key = $entryKey, subkey = $entrySubkey}"
     )
-    if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
+    if (entryKey != null && entrySubkey != null) {
       CredentialManagerRepo.getInstance().onOptionSelected(
         uiState.activeEntry?.activeProvider!!.name,
-        createOptionEntryKey,
-        createOptionEntrySubkey
+        entryKey,
+        entrySubkey
       )
     } else {
       TODO("Gracefully handle illegal state.")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index e3398c0..23592c3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -16,25 +16,29 @@
 
 package com.android.credentialmanager.getflow
 
+import android.text.TextUtils
+
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Card
-import androidx.compose.material.Chip
-import androidx.compose.material.ChipDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.Text
-import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Card
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
@@ -45,13 +49,14 @@
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.R
-import com.android.credentialmanager.createflow.CancelButton
-import com.android.credentialmanager.ui.theme.Grey100
-import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.Typography
-import com.android.credentialmanager.ui.theme.lightBackgroundColor
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
+import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
 
-@ExperimentalMaterialApi
+
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun GetCredentialScreen(
   viewModel: GetCredentialViewModel,
@@ -65,18 +70,22 @@
     sheetContent = {
       val uiState = viewModel.uiState
       when (uiState.currentScreenState) {
-        GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
+        GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
           requestDisplayInfo = uiState.requestDisplayInfo,
-          providerInfo = uiState.selectedProvider!!,
+          sortedUserNameToCredentialEntryList = uiState.sortedUserNameToCredentialEntryList,
+          onEntrySelected = viewModel::onEntrySelected,
           onCancel = viewModel::onCancel,
-          onOptionSelected = viewModel::onCredentailSelected,
-          multiProvider = uiState.providers.size > 1,
           onMoreOptionSelected = viewModel::onMoreOptionSelected,
         )
+        GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
+          sortedUserNameToCredentialEntryList = uiState.sortedUserNameToCredentialEntryList,
+          onEntrySelected = viewModel::onEntrySelected,
+          onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+        )
       }
     },
     scrimColor = Color.Transparent,
-    sheetShape = Shapes.medium,
+    sheetShape = MaterialTheme.shapes.medium,
   ) {}
   LaunchedEffect(state.currentValue) {
     if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -85,44 +94,35 @@
   }
 }
 
-@ExperimentalMaterialApi
+/** Draws the primary credential selection page. */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun CredentialSelectionCard(
+fun PrimarySelectionCard(
   requestDisplayInfo: RequestDisplayInfo,
-  providerInfo: ProviderInfo,
-  onOptionSelected: (String, String) -> Unit,
+  sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
+  onEntrySelected: (EntryInfo) -> Unit,
   onCancel: () -> Unit,
-  multiProvider: Boolean,
   onMoreOptionSelected: () -> Unit,
 ) {
-  Card(
-    backgroundColor = lightBackgroundColor,
-  ) {
+  Card() {
     Column() {
-      Icon(
-        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
-        contentDescription = null,
-        tint = Color.Unspecified,
-        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
-      )
       Text(
-        text = stringResource(R.string.choose_sign_in_title),
-        style = Typography.subtitle1,
-        modifier = Modifier
-          .padding(all = 24.dp)
-          .align(alignment = Alignment.CenterHorizontally)
+        text = stringResource(
+          if (sortedUserNameToCredentialEntryList.size == 1) {
+            if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
+                .first().credentialType == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
+            )
+              R.string.get_dialog_title_use_passkey_for
+            else R.string.get_dialog_title_use_sign_in_for
+          } else R.string.get_dialog_title_choose_sign_in_for,
+          requestDisplayInfo.appDomainName
+        ),
+        style = MaterialTheme.typography.titleMedium,
+        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
-      Text(
-        text = requestDisplayInfo.appDomainName,
-        style = Typography.body2,
-        modifier = Modifier.padding(horizontal = 28.dp)
-      )
-      Divider(
-        thickness = 24.dp,
-        color = Color.Transparent
-      )
+
       Card(
-        shape = Shapes.medium,
+        shape = MaterialTheme.shapes.large,
         modifier = Modifier
           .padding(horizontal = 24.dp)
           .align(alignment = Alignment.CenterHorizontally)
@@ -130,15 +130,14 @@
         LazyColumn(
           verticalArrangement = Arrangement.spacedBy(2.dp)
         ) {
-          providerInfo.credentialOptions.forEach {
-            item {
-              CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected)
-            }
+          items(sortedUserNameToCredentialEntryList) {
+            CredentialEntryRow(
+              credentialEntryInfo = it.sortedCredentialEntryList.first(),
+              onEntrySelected = onEntrySelected
+            )
           }
-          if (multiProvider) {
-            item {
-              MoreOptionRow(onSelect = onMoreOptionSelected)
-            }
+          item {
+            SignInAnotherWayRow(onSelect = onMoreOptionSelected)
           }
         }
       }
@@ -161,57 +160,127 @@
   }
 }
 
-@ExperimentalMaterialApi
+/** Draws the secondary credential selection page, where all sign-in options are listed. */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun CredentialOptionRow(
-    credentialOptionInfo: CredentialOptionInfo,
-    onOptionSelected: (String, String) -> Unit,
+fun AllSignInOptionCard(
+  sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>,
+  onEntrySelected: (EntryInfo) -> Unit,
+  onBackButtonClicked: () -> Unit,
 ) {
-  Chip(
-    modifier = Modifier.fillMaxWidth(),
-    onClick = {onOptionSelected(credentialOptionInfo.entryKey, credentialOptionInfo.entrySubkey)},
-    leadingIcon = {
-      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-            bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
-        // TODO: add description.
-            contentDescription = "")
-    },
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
+  Card() {
     Column() {
-      Text(
-        text = credentialOptionInfo.title,
-        style = Typography.h6,
-        modifier = Modifier.padding(top = 16.dp)
+      TopAppBar(
+        colors = TopAppBarDefaults.smallTopAppBarColors(
+          containerColor = Color.Transparent,
+        ),
+        title = {
+          Text(
+            text = stringResource(R.string.get_dialog_title_sign_in_options),
+            style = MaterialTheme.typography.titleMedium
+          )
+        },
+        navigationIcon = {
+          IconButton(onClick = onBackButtonClicked) {
+            Icon(
+              Icons.Filled.ArrowBack,
+              contentDescription = stringResource(R.string.accessibility_back_arrow_button))
+          }
+        },
+        modifier = Modifier.padding(top = 12.dp)
       )
-      Text(
-        text = credentialOptionInfo.subtitle,
-        style = Typography.body2,
-        modifier = Modifier.padding(bottom = 16.dp)
-      )
+
+      Card(
+        shape = MaterialTheme.shapes.large,
+        modifier = Modifier
+          .padding(start = 24.dp, end = 24.dp, bottom = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(8.dp)
+        ) {
+          items(sortedUserNameToCredentialEntryList) { item ->
+            PerUserNameCredentials(
+              perUserNameCredentialEntryList = item,
+              onEntrySelected = onEntrySelected
+            )
+          }
+        }
+      }
     }
   }
 }
 
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun MoreOptionRow(onSelect: () -> Unit) {
-  Chip(
-    modifier = Modifier.fillMaxWidth().height(52.dp),
-    onClick = onSelect,
-    colors = ChipDefaults.chipColors(
-      backgroundColor = Grey100,
-      leadingIconContentColor = Grey100
-    ),
-    shape = Shapes.large
-  ) {
-    Text(
-      text = stringResource(R.string.string_more_options),
-      style = Typography.h6,
-    )
+fun PerUserNameCredentials(
+  perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
+  onEntrySelected: (EntryInfo) -> Unit,
+) {
+  Text(
+    text = stringResource(
+      R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName),
+    style = MaterialTheme.typography.labelLarge,
+    modifier = Modifier.padding(vertical = 8.dp)
+  )
+  perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
+    CredentialEntryRow(it, onEntrySelected)
   }
 }
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CredentialEntryRow(
+  credentialEntryInfo: CredentialEntryInfo,
+  onEntrySelected: (EntryInfo) -> Unit,
+) {
+  SuggestionChip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onEntrySelected(credentialEntryInfo)},
+    icon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+        bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
+        // TODO: add description.
+        contentDescription = "")
+    },
+    shape = MaterialTheme.shapes.large,
+    label = {
+      Column() {
+        // TODO: fix the text values.
+        Text(
+          text = credentialEntryInfo.userName,
+          style = MaterialTheme.typography.titleLarge,
+          modifier = Modifier.padding(top = 16.dp)
+        )
+        Text(
+          text =
+          if (TextUtils.isEmpty(credentialEntryInfo.displayName))
+            credentialEntryInfo.credentialTypeDisplayName
+          else
+            credentialEntryInfo.credentialTypeDisplayName +
+                    stringResource(R.string.get_dialog_sign_in_type_username_separator) +
+                    credentialEntryInfo.displayName,
+          style = MaterialTheme.typography.bodyMedium,
+          modifier = Modifier.padding(bottom = 16.dp)
+        )
+      }
+    }
+  )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SignInAnotherWayRow(onSelect: () -> Unit) {
+  SuggestionChip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = onSelect,
+    shape = MaterialTheme.shapes.large,
+    label = {
+      Text(
+        text = stringResource(R.string.get_dialog_use_saved_passkey_for),
+        style = MaterialTheme.typography.titleLarge,
+        modifier = Modifier.padding(vertical = 16.dp)
+      )
+    }
+  )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 7b6c30a..f449274 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -26,12 +26,18 @@
 import com.android.credentialmanager.CredentialManagerRepo
 import com.android.credentialmanager.common.DialogResult
 import com.android.credentialmanager.common.ResultState
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
 
 data class GetCredentialUiState(
-  val providers: List<ProviderInfo>,
+  val providerInfoList: List<ProviderInfo>,
   val currentScreenState: GetScreenState,
   val requestDisplayInfo: RequestDisplayInfo,
-  val selectedProvider: ProviderInfo? = null,
+  /**
+   * The credential entries grouped by userName, derived from all entries of the [providerInfoList].
+   * Note that the list order matters to the display order.
+   */
+  val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList> =
+    createSortedUserNameToCredentialEntryList(providerInfoList),
 )
 
 class GetCredentialViewModel(
@@ -49,20 +55,28 @@
     return dialogResult
   }
 
-  fun onCredentailSelected(entryKey: String, entrySubkey: String) {
-    Log.d("Account Selector", "credential selected: {key=$entryKey,subkey=$entrySubkey}")
+  fun onEntrySelected(entry: EntryInfo) {
+    Log.d("Account Selector", "credential selected:" +
+            " {provider=${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
     CredentialManagerRepo.getInstance().onOptionSelected(
-      uiState.selectedProvider!!.name,
-      entryKey,
-      entrySubkey
+      entry.providerId,
+      entry.entryKey,
+      entry.entrySubkey
     )
-    dialogResult.value = DialogResult(
-      ResultState.COMPLETE,
-    )
+    dialogResult.value = DialogResult(ResultState.COMPLETE)
   }
 
   fun onMoreOptionSelected() {
     Log.d("Account Selector", "More Option selected")
+    uiState = uiState.copy(
+      currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS
+    )
+  }
+
+  fun onBackToPrimarySelectionScreen() {
+    uiState = uiState.copy(
+      currentScreenState = GetScreenState.PRIMARY_SELECTION
+    )
   }
 
   fun onCancel() {
@@ -70,3 +84,63 @@
     dialogResult.value = DialogResult(ResultState.CANCELED)
   }
 }
+
+internal fun createSortedUserNameToCredentialEntryList(
+  providerInfoList: List<ProviderInfo>
+): List<PerUserNameCredentialEntryList> {
+  // Group by username
+  val userNameToEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
+  providerInfoList.forEach { providerInfo ->
+    providerInfo.credentialEntryList.forEach {
+      userNameToEntryMap.compute(
+        it.userName
+      ) {
+          _, v ->
+        if (v == null) {
+          mutableListOf(it)
+        } else {
+          v.add(it)
+          v
+        }
+      }
+    }
+  }
+  val comparator = CredentialEntryInfoComparator()
+  // Sort per username
+  userNameToEntryMap.values.forEach {
+    it.sortWith(comparator)
+  }
+  // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+  return userNameToEntryMap.map {
+    PerUserNameCredentialEntryList(it.key, it.value)
+  }.sortedWith(
+    compareBy(comparator) { it.sortedCredentialEntryList.first() }
+  )
+}
+
+internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
+  override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
+    // First order by last used timestamp
+    if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
+      if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
+        return 1
+      } else if (p0.lastUsedTimeMillis > p1.lastUsedTimeMillis) {
+        return -1
+      }
+    } else if (p0.lastUsedTimeMillis != null && p0.lastUsedTimeMillis > 0) {
+      return -1
+    } else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) {
+      return 1
+    }
+
+    // Then prefer passkey type for its security benefits
+    if (p0.credentialType != p1.credentialType) {
+      if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
+        return -1
+      } else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
+        return 1
+      }
+    }
+    return 0
+  }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b6ecd37..84009b1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -19,30 +19,74 @@
 import android.graphics.drawable.Drawable
 
 data class ProviderInfo(
+  /**
+   * Unique id (component name) of this provider.
+   * Not for display purpose - [displayName] should be used for ui rendering.
+   */
+  val id: String,
   val icon: Drawable,
-  val name: String,
   val displayName: String,
-  val credentialTypeIcon: Drawable,
-  val credentialOptions: List<CredentialOptionInfo>,
+  val credentialEntryList: List<CredentialEntryInfo>,
+  val authenticationEntry: AuthenticationEntryInfo?,
+  val actionEntryList: List<ActionEntryInfo>,
+  // TODO: add remote entry
 )
 
-data class CredentialOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+abstract class EntryInfo (
+  /** Unique id combination of this entry. Not for display purpose. */
+  val providerId: String,
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
 )
 
-data class RequestDisplayInfo(
+class CredentialEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  /** Type of this credential used for sorting. Not localized so must not be directly displayed. */
+  val credentialType: String,
+  /** Localized type value of this credential used for display purpose. */
+  val credentialTypeDisplayName: String,
   val userName: String,
-  val displayName: String,
-  val type: String,
+  val displayName: String?,
+  val icon: Drawable,
+  val lastUsedTimeMillis: Long?,
+) : EntryInfo(providerId, entryKey, entrySubkey)
+
+// TODO: handle sub credential type values like password obfuscation.
+
+class AuthenticationEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+) : EntryInfo(providerId, entryKey, entrySubkey)
+
+class ActionEntryInfo(
+  providerId: String,
+  entryKey: String,
+  entrySubkey: String,
+  val title: String,
+  val subTitle: String?,
+) : EntryInfo(providerId, entryKey, entrySubkey)
+
+data class RequestDisplayInfo(
   val appDomainName: String,
 )
 
+/**
+ * @property userName the userName that groups all the entries in this list
+ * @property sortedCredentialEntryList the credential entries associated with the [userName] sorted
+ *                                     by last used timestamps and then by credential types
+ */
+data class PerUserNameCredentialEntryList(
+  val userName: String,
+  val sortedCredentialEntryList: List<CredentialEntryInfo>,
+)
+
 /** The name of the current screen. */
 enum class GetScreenState {
-  CREDENTIAL_SELECTION,
+  /** The primary credential selection page. */
+  PRIMARY_SELECTION,
+  /** The secondary credential selection page, where all sign-in options are listed. */
+  ALL_SIGN_IN_OPTIONS,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
index 12ab436..dfbcae1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
@@ -17,6 +17,7 @@
 package com.android.credentialmanager.jetpack.provider
 
 import android.app.slice.Slice
+import android.credentials.ui.Entry
 import android.graphics.drawable.Icon
 
 /**
@@ -24,23 +25,46 @@
  *
  * TODO: move to jetpack.
  */
-abstract class CredentialEntryUi(
-  val credentialTypeIcon: Icon,
-  val profileIcon: Icon?,
+class CredentialEntryUi(
+  val credentialType: CharSequence,
+  val credentialTypeDisplayName: CharSequence,
+  val userName: CharSequence,
+  val userDisplayName: CharSequence?,
+  val entryIcon: Icon,
   val lastUsedTimeMillis: Long?,
   val note: CharSequence?,
 ) {
   companion object {
     fun fromSlice(slice: Slice): CredentialEntryUi {
-      return when (slice.spec?.type) {
-        TYPE_PUBLIC_KEY_CREDENTIAL -> PasskeyCredentialEntryUi.fromSlice(slice)
-        TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntryUi.fromSlice(slice)
-        else -> throw IllegalArgumentException("Unexpected type: ${slice.spec?.type}")
-      }
-    }
+      var credentialType = slice.spec!!.type
+      var credentialTypeDisplayName: CharSequence? = null
+      var userName: CharSequence? = null
+      var userDisplayName: CharSequence? = null
+      var entryIcon: Icon? = null
+      var lastUsedTimeMillis: Long? = null
+      var note: CharSequence? = null
 
-    const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
-      "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
-    const val TYPE_PASSWORD_CREDENTIAL: String = "androidx.credentials.TYPE_PASSWORD"
+      val items = slice.items
+      items.forEach {
+        if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)) {
+          credentialTypeDisplayName = it.text
+        } else if (it.hasHint(Entry.HINT_USER_NAME)) {
+          userName = it.text
+        } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
+          userDisplayName = it.text
+        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
+          entryIcon = it.icon
+        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
+          lastUsedTimeMillis = it.long
+        } else if (it.hasHint(Entry.HINT_NOTE)) {
+          note = it.text
+        }
+      }
+
+      return CredentialEntryUi(
+        credentialType, credentialTypeDisplayName!!, userName!!, userDisplayName, entryIcon!!,
+        lastUsedTimeMillis, note,
+      )
+    }
   }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
deleted file mode 100644
index c5dbe66..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
+++ /dev/null
@@ -1,63 +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.credentialmanager.jetpack.provider
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-class PasskeyCredentialEntryUi(
-  val userName: CharSequence,
-  val userDisplayName: CharSequence?,
-  credentialTypeIcon: Icon,
-  profileIcon: Icon?,
-  lastUsedTimeMillis: Long?,
-  note: CharSequence?,
-) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
-  companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      var userName: CharSequence? = null
-      var userDisplayName: CharSequence? = null
-      var credentialTypeIcon: Icon? = null
-      var profileIcon: Icon? = null
-      var lastUsedTimeMillis: Long? = null
-      var note: CharSequence? = null
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_USER_NAME)) {
-          userName = it.text
-        } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) {
-          userDisplayName = it.text
-        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
-          credentialTypeIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          profileIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        } else if (it.hasHint(Entry.HINT_NOTE)) {
-          note = it.text
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return PasskeyCredentialEntryUi(
-        userName!!, userDisplayName, credentialTypeIcon!!,
-        profileIcon, lastUsedTimeMillis, note,
-      )
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
deleted file mode 100644
index 5049503..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
+++ /dev/null
@@ -1,68 +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.credentialmanager.jetpack.provider
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a password credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class PasswordCredentialEntryUi(
-  val userName: CharSequence,
-  val password: CharSequence,
-  credentialTypeIcon: Icon,
-  profileIcon: Icon?,
-  lastUsedTimeMillis: Long?,
-  note: CharSequence?,
-) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) {
-  companion object {
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      var userName: CharSequence? = null
-      var password: CharSequence? = null
-      var credentialTypeIcon: Icon? = null
-      var profileIcon: Icon? = null
-      var lastUsedTimeMillis: Long? = null
-      var note: CharSequence? = null
-
-      val items = slice.items
-      items.forEach {
-        if (it.hasHint(Entry.HINT_USER_NAME)) {
-          userName = it.text
-        } else if (it.hasHint(Entry.HINT_PASSWORD_VALUE)) {
-          password = it.text
-        } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) {
-          credentialTypeIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) {
-          profileIcon = it.icon
-        } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        } else if (it.hasHint(Entry.HINT_NOTE)) {
-          note = it.text
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return PasswordCredentialEntryUi(
-        userName!!, password!!, credentialTypeIcon!!,
-        profileIcon, lastUsedTimeMillis, note,
-      )
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index cba8658..5ea6993 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -1,7 +1,7 @@
 package com.android.credentialmanager.ui.theme
 
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Shapes
+import androidx.compose.material3.Shapes
 import androidx.compose.ui.unit.dp
 
 val Shapes = Shapes(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index a9d20ae..248df92 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -1,30 +1,21 @@
 package com.android.credentialmanager.ui.theme
 
 import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.darkColorScheme
 import androidx.compose.runtime.Composable
 
-private val DarkColorPalette = darkColors(
+private val AppDarkColorScheme = darkColorScheme(
   primary = Purple200,
-  primaryVariant = Purple700,
-  secondary = Teal200
+  secondary = Purple700,
+  tertiary = Teal200
 )
 
-private val LightColorPalette = lightColors(
+private val AppLightColorScheme = lightColorScheme(
   primary = Purple500,
-  primaryVariant = Purple700,
-  secondary = Teal200
-
-  /* Other default colors to override
-    background = Color.White,
-    surface = Color.White,
-    onPrimary = Color.White,
-    onSecondary = Color.Black,
-    onBackground = Color.Black,
-    onSurface = Color.Black,
-    */
+  secondary = Purple700,
+  tertiary = Teal200
 )
 
 @Composable
@@ -32,14 +23,14 @@
   darkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable () -> Unit
 ) {
-  val colors = if (darkTheme) {
-    DarkColorPalette
+  val AppColorScheme = if (darkTheme) {
+    AppDarkColorScheme
   } else {
-    LightColorPalette
+    AppLightColorScheme
   }
 
   MaterialTheme(
-    colors = colors,
+    colorScheme = AppColorScheme,
     typography = Typography,
     shapes = Shapes,
     content = content
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
index d8fb01c..e09abbb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
@@ -1,6 +1,6 @@
 package com.android.credentialmanager.ui.theme
 
-import androidx.compose.material.Typography
+import androidx.compose.material3.Typography
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
@@ -8,32 +8,32 @@
 
 // Set of Material typography styles to start with
 val Typography = Typography(
-  subtitle1 = TextStyle(
+  titleMedium = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Normal,
     fontSize = 24.sp,
     lineHeight = 32.sp,
   ),
-  body1 = TextStyle(
+  bodyLarge = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Normal,
     fontSize = 14.sp,
     lineHeight = 20.sp,
   ),
-  body2 = TextStyle(
+  bodyMedium = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Normal,
     fontSize = 14.sp,
     lineHeight = 20.sp,
     color = textColorSecondary
   ),
-  button = TextStyle(
+  labelLarge = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Medium,
     fontSize = 14.sp,
     lineHeight = 20.sp,
   ),
-  h6 = TextStyle(
+  titleLarge = TextStyle(
     fontFamily = FontFamily.Default,
     fontWeight = FontWeight.Medium,
     fontSize = 16.sp,
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 688d116..6a3b239 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -189,6 +189,9 @@
     <!-- Text to show in warning dialog on the tv when the app source is not trusted [CHAR LIMIT=NONE] -->
     <string name="untrusted_external_source_warning" product="tv">For your security, your TV currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
 
+    <!-- Text to show in warning dialog on the wear when the app source is not trusted [CHAR LIMIT=NONE] -->
+    <string name="untrusted_external_source_warning" product="watch">For your security, your watch currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
+
     <!-- Text to show in warning dialog on the phone when the app source is not trusted [CHAR LIMIT=NONE] -->
     <string name="untrusted_external_source_warning" product="default">For your security, your phone currently isn’t allowed to install unknown apps from this source. You can change this in Settings.</string>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index de76632..fa93670 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -124,9 +124,8 @@
     private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
     private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
     private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
-    private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
-    private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
-    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;
+    private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 7;
+    private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 8;
 
     // If unknown sources are temporary allowed
     private boolean mAllowUnknownSources;
@@ -189,8 +188,6 @@
             case DLG_INSTALL_ERROR:
                 return InstallErrorDialog.newInstance(
                         mPm.getApplicationLabel(mPkgInfo.applicationInfo));
-            case DLG_NOT_SUPPORTED_ON_WEAR:
-                return NotSupportedOnWearDialog.newInstance();
             case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
                 return SimpleErrorDialog.newInstance(
                         R.string.install_apps_user_restriction_dlg_text);
@@ -379,12 +376,8 @@
             return;
         }
 
-        if (DeviceUtils.isWear(this)) {
-            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
-            return;
-        }
-
         final boolean wasSetUp = processAppSnippet(packageSource);
+
         if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
 
         if (!wasSetUp) {
@@ -779,21 +772,6 @@
     }
 
     /**
-     * An error dialog shown when the app is not supported on wear
-     */
-    public static class NotSupportedOnWearDialog extends SimpleErrorDialog {
-        static SimpleErrorDialog newInstance() {
-            return SimpleErrorDialog.newInstance(R.string.wear_not_allowed_dlg_text);
-        }
-
-        @Override
-        public void onCancel(DialogInterface dialog) {
-            getActivity().setResult(RESULT_OK);
-            getActivity().finish();
-        }
-    }
-
-    /**
      * An error dialog shown when the device is out of space
      */
     public static class OutOfSpaceDialog extends AppErrorDialog {
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index f0b6ac5..e04a9be 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -57,6 +57,5 @@
 }
 
 dependencies {
-    implementation(project(":spa"))
-    implementation "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
+    implementation project(":spa")
 }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 682d3fc..7a20c747 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -60,9 +60,9 @@
     api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
     api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
     api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
-    api "androidx.navigation:navigation-compose:2.5.0"
+    api "androidx.navigation:navigation-compose:2.6.0-alpha03"
+    api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
     api "com.google.android.material:material:1.7.0-alpha03"
     debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
     implementation "com.airbnb.android:lottie-compose:5.2.0"
-    implementation "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index e63e4c9..14b1629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -30,10 +30,14 @@
     val injectEntry: SettingsEntry,
 )
 
+private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String {
+    return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments)
+}
+
 /**
  * The repository to maintain all Settings entries
  */
-class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
+class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) {
     // Map of entry unique Id to entry
     private val entryMap: Map<String, SettingsEntry>
 
@@ -118,8 +122,9 @@
         return entryPath.map {
             if (it.toPage == null)
                 defaultTitle
-            else
-                it.toPage.getTitle()!!
+            else {
+                it.toPage.getTitle(sppRepository)
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2fa9229..bb287d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -95,11 +95,6 @@
         return false
     }
 
-    fun getTitle(): String? {
-        val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
-        return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
-    }
-
     fun enterPage() {
         SpaEnvironmentFactory.instance.logger.event(
             id,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
new file mode 100644
index 0000000..9419161
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryRepositoryTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+    private val entryRepository by spaEnvironment.entryRepository
+
+    @Test
+    fun testGetPageWithEntry() {
+        val pageWithEntry = entryRepository.getAllPageWithEntry()
+        assertThat(pageWithEntry.size).isEqualTo(3)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+                ?.entries?.size
+        ).isEqualTo(1)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+                ?.entries?.size
+        ).isEqualTo(3)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+                ?.entries?.size
+        ).isEqualTo(2)
+        assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+    }
+
+    @Test
+    fun testGetEntry() {
+        val entry = entryRepository.getAllEntries()
+        assertThat(entry.size).isEqualTo(7)
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "ROOT",
+                    SppHome.createSettingsPage(),
+                    SettingsPage.createNull(),
+                    SppHome.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer1.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer2.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                    SppLayer2.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+            )
+        ).isNotNull()
+    }
+
+    @Test
+    fun testGetEntryPath() {
+        assertThat(
+            entryRepository.getEntryPathWithDisplayName(
+                getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+            )
+        ).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
+            .inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithTitle(
+                getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+                "entryTitle"
+            )
+        ).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithDisplayName(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer1.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                )
+            )
+        ).containsExactly("INJECT_SppLayer1", "ROOT_SppHome").inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithTitle(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer2.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                    SppLayer2.createSettingsPage(),
+                ),
+                "defaultTitle"
+            )
+        ).containsExactly("SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
new file mode 100644
index 0000000..31d2ae4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.os.bundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val INJECT_ENTRY_NAME = "INJECT"
+const val ROOT_ENTRY_NAME = "ROOT"
+
+class MacroForTest(private val pageId: String, private val entryId: String) : EntryMacro {
+    @Composable
+    override fun UiLayout() {
+        val entryData = LocalEntryDataProvider.current
+        assertThat(entryData.isHighlighted).isFalse()
+        assertThat(entryData.pageId).isEqualTo(pageId)
+        assertThat(entryData.entryId).isEqualTo(entryId)
+    }
+
+    override fun getSearchData(): EntrySearchData {
+        return EntrySearchData("myTitle")
+    }
+
+    override fun getStatusData(): EntryStatusData {
+        return EntryStatusData(isDisabled = true, isSwitchOff = true)
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun testBuildBasic() {
+        val owner = SettingsPage.create("mySpp")
+        val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
+        assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry.displayName).isEqualTo("myEntry")
+        assertThat(entry.owner.sppName).isEqualTo("mySpp")
+        assertThat(entry.owner.displayName).isEqualTo("mySpp")
+        assertThat(entry.fromPage).isNull()
+        assertThat(entry.toPage).isNull()
+        assertThat(entry.isAllowSearch).isFalse()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.mutableStatus).isFalse()
+    }
+
+    @Test
+    fun testBuildWithLink() {
+        val owner = SettingsPage.create("mySpp")
+        val fromPage = SettingsPage.create("fromSpp")
+        val toPage = SettingsPage.create("toSpp")
+        val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
+            .setLink(toPage = toPage).build()
+        assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+        assertThat(entryFrom.displayName).isEqualTo("myEntry")
+        assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
+        assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
+
+        val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
+            .setLink(fromPage = fromPage).build()
+        assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+        assertThat(entryTo.displayName).isEqualTo("myEntry")
+        assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
+        assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
+    }
+
+    @Test
+    fun testBuildInject() {
+        val owner = SettingsPage.create("mySpp")
+        val entryInject = SettingsEntryBuilder.createInject(owner).build()
+        assertThat(entryInject.id).isEqualTo(
+            getUniqueEntryId(
+                INJECT_ENTRY_NAME,
+                owner,
+                toPage = owner
+            )
+        )
+        assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME}_mySpp")
+        assertThat(entryInject.fromPage).isNull()
+        assertThat(entryInject.toPage).isNotNull()
+    }
+
+    @Test
+    fun testBuildRoot() {
+        val owner = SettingsPage.create("mySpp")
+        val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
+        assertThat(entryInject.id).isEqualTo(
+            getUniqueEntryId(
+                ROOT_ENTRY_NAME,
+                owner,
+                toPage = owner
+            )
+        )
+        assertThat(entryInject.displayName).isEqualTo("myRootEntry")
+        assertThat(entryInject.fromPage).isNull()
+        assertThat(entryInject.toPage).isNotNull()
+    }
+
+    @Test
+    fun testSetAttributes() {
+        val owner = SettingsPage.create("mySpp")
+        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+            .setDisplayName("myEntryDisplay")
+            .setIsAllowSearch(true)
+            .setIsSearchDataDynamic(false)
+            .setHasMutableStatus(true)
+            .build()
+        assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry.displayName).isEqualTo("myEntryDisplay")
+        assertThat(entry.fromPage).isNull()
+        assertThat(entry.toPage).isNull()
+        assertThat(entry.isAllowSearch).isTrue()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.mutableStatus).isTrue()
+    }
+
+    @Test
+    fun testSetMarco() {
+        val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
+        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+            .setMacro {
+                assertThat(it?.getString("param")).isEqualTo("v1")
+                assertThat(it?.getString("rtParam")).isEqualTo("v2")
+                assertThat(it?.getString("unknown")).isNull()
+                MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
+            }
+            .build()
+
+        val rtArguments = bundleOf("rtParam" to "v2")
+        composeTestRule.setContent { entry.UiLayout(rtArguments) }
+        val searchData = entry.getSearchData(rtArguments)
+        val statusData = entry.getStatusData(rtArguments)
+        assertThat(searchData?.title).isEqualTo("myTitle")
+        assertThat(searchData?.keyword).isEmpty()
+        assertThat(statusData?.isDisabled).isTrue()
+        assertThat(statusData?.isSwitchOff).isTrue()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
new file mode 100644
index 0000000..6c0c652
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageProviderRepositoryTest {
+    @Test
+    fun getStartPageTest() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+        assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
+        assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+
+        val sppRepoNull =
+            SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+        assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+        assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+
+        val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
+        val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+        val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
+        val allRoots = sppRepo.getAllRootPages()
+        assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+        assertThat(allRoots.size).isEqualTo(2)
+        assertThat(allRoots).contains(rootPage1)
+        assertThat(allRoots).contains(rootPage2)
+    }
+
+    @Test
+    fun getProviderTest() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+        assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
+        assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+
+        val sppRepo = SettingsPageProviderRepository(listOf(
+            object : SettingsPageProvider {
+                override val name = "Spp"
+            }
+        ), emptyList())
+        assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+        assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
+        assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
new file mode 100644
index 0000000..539e56b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaLogger = SpaLoggerForTest()
+    private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+    @Test
+    fun testNullPage() {
+        val page = SettingsPage.createNull()
+        assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+        assertThat(page.sppName).isEqualTo("NULL")
+        assertThat(page.displayName).isEqualTo("NULL")
+        assertThat(page.buildRoute()).isEqualTo("NULL")
+        assertThat(page.isCreateBy("NULL")).isTrue()
+        assertThat(page.isCreateBy("Spp")).isFalse()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+    }
+
+    @Test
+    fun testRegularPage() {
+        val page = SettingsPage.create("mySpp", "SppDisplayName")
+        assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+        assertThat(page.sppName).isEqualTo("mySpp")
+        assertThat(page.displayName).isEqualTo("SppDisplayName")
+        assertThat(page.buildRoute()).isEqualTo("mySpp")
+        assertThat(page.isCreateBy("NULL")).isFalse()
+        assertThat(page.isCreateBy("mySpp")).isTrue()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+            "-e spaActivityDestination mySpp"
+        )
+    }
+
+    @Test
+    fun testParamPage() {
+        val arguments = bundleOf(
+            "string_param" to "myStr",
+            "int_param" to 10,
+        )
+        val page = spaEnvironment.createPage("SppWithParam", arguments)
+        assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+        ), arguments))
+        assertThat(page.sppName).isEqualTo("SppWithParam")
+        assertThat(page.displayName).isEqualTo("SppWithParam")
+        assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
+        assertThat(page.isCreateBy("SppWithParam")).isTrue()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+            "-e spaActivityDestination SppWithParam/myStr/10"
+        )
+    }
+
+    @Test
+    fun testRtParamPage() {
+        val arguments = bundleOf(
+            "string_param" to "myStr",
+            "int_param" to 10,
+            "rt_param" to "rtStr",
+        )
+        val page = spaEnvironment.createPage("SppWithRtParam", arguments)
+        assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+            navArgument("rt_param") { type = NavType.StringType },
+        ), arguments))
+        assertThat(page.sppName).isEqualTo("SppWithRtParam")
+        assertThat(page.displayName).isEqualTo("SppWithRtParam")
+        assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
+        assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
+        assertThat(page.hasRuntimeParam()).isTrue()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+    }
+
+    @Test
+    fun testPageEvent() {
+        spaLogger.reset()
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val page = spaEnvironment.createPage("SppHome")
+        page.enterPage()
+        page.leavePage()
+        page.enterPage()
+        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+            .isEqualTo(2)
+        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+            .isEqualTo(1)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..b8731a3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+
+class SpaLoggerForTest : SpaLogger {
+    data class MsgCountKey(val msg: String, val category: LogCategory)
+    data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
+
+    private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
+    private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
+
+    override fun message(tag: String, msg: String, category: LogCategory) {
+        val key = MsgCountKey("[$tag]$msg", category)
+        messageCount[key] = messageCount.getOrDefault(key, 0) + 1
+    }
+
+    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+        val key = EventCountKey(id, event, category)
+        eventCount[key] = eventCount.getOrDefault(key, 0) + 1
+    }
+
+    fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+        val key = MsgCountKey("[$tag]$msg", category)
+        return messageCount.getOrDefault(key, 0)
+    }
+
+    fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+        val key = EventCountKey(id, event, category)
+        return eventCount.getOrDefault(key, 0)
+    }
+
+    fun reset() {
+        messageCount.clear()
+        eventCount.clear()
+    }
+}
+
+class MockActivity : BrowseActivity()
+
+object SppHome : SettingsPageProvider {
+    override val name = "SppHome"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleHome"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SppLayer1.buildInject().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
+object SppLayer1 : SettingsPageProvider {
+    override val name = "SppLayer1"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleLayer1"
+    }
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
+            SppLayer2.buildInject().setLink(fromPage = owner).build(),
+            SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
+        )
+    }
+}
+
+object SppLayer2 : SettingsPageProvider {
+    override val name = "SppLayer2"
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
+            SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+        )
+    }
+}
+
+class SpaEnvironmentForTest(
+    context: Context,
+    override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
+    override val logger: SpaLogger = SpaLoggerForTest()
+) : SpaEnvironment(context) {
+
+    override val pageProviderRepository = lazy {
+        SettingsPageProviderRepository(
+            listOf(
+                SppHome, SppLayer1, SppLayer2,
+                object : SettingsPageProvider {
+                    override val name = "SppWithParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                    )
+                },
+                object : SettingsPageProvider {
+                    override val name = "SppWithRtParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                        navArgument("rt_param") { type = NavType.StringType },
+                    )
+                },
+            ),
+            listOf(SettingsPage.create("SppHome"))
+        )
+    }
+
+    fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
+        return pageProviderRepository.value
+            .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
new file mode 100644
index 0000000..93f9afe
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.normalize
+
+fun getUniquePageId(
+    name: String,
+    parameter: List<NamedNavArgument> = emptyList(),
+    arguments: Bundle? = null
+): String {
+    val normArguments = parameter.normalize(arguments)
+    return "$name:${normArguments?.toString()}".toHashId()
+}
+
+fun getUniquePageId(page: SettingsPage): String {
+    return getUniquePageId(page.sppName, page.parameter, page.arguments)
+}
+
+fun getUniqueEntryId(
+    name: String,
+    owner: SettingsPage,
+    fromPage: SettingsPage? = null,
+    toPage: SettingsPage? = null
+): String {
+    val ownerId = getUniquePageId(owner)
+    val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
+    val toId = if (toPage == null) "null" else getUniquePageId(toPage)
+    return "$name:$ownerId($fromId-$toId)".toHashId()
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 21ff085..48ebd8d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -20,9 +20,12 @@
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@RunWith(AndroidJUnit4::class)
 class ParameterTest {
     @Test
     fun navRouteTest() {
@@ -88,14 +91,14 @@
             "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
         )
 
-        val setParialParam = navArguments.normalize(
+        val setPartialParam = navArguments.normalize(
             bundleOf(
                 "string_param" to "myStr",
                 "rt_param" to "rtStr",
             )
         )
-        assertThat(setParialParam).isNotNull()
-        assertThat(setParialParam.toString()).isEqualTo(
+        assertThat(setPartialParam).isNotNull()
+        assertThat(setPartialParam.toString()).isEqualTo(
             "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
         )
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 3cd8378..9d6b311 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -41,6 +41,13 @@
 import kotlinx.coroutines.Dispatchers
 
 private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+internal data class AppListState(
+    val showSystem: State<Boolean>,
+    val option: State<Int>,
+    val searchQuery: State<String>,
+)
 
 /**
  * The template to render an App List.
@@ -49,23 +56,26 @@
  */
 @Composable
 internal fun <T : AppRecord> AppList(
-    appListConfig: AppListConfig,
+    config: AppListConfig,
     listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
+    state: AppListState,
+    header: @Composable () -> Unit,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
     bottomPadding: Dp,
+    appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
+        loadAppListData(config, listModel, state)
+    },
 ) {
-    LogCompositions(TAG, appListConfig.userId.toString())
-    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
-    AppListWidget(appListData, listModel, appItem, bottomPadding)
+    LogCompositions(TAG, config.userId.toString())
+    val appListData = appListDataSupplier()
+    AppListWidget(appListData, listModel, header, appItem, bottomPadding)
 }
 
 @Composable
 private fun <T : AppRecord> AppListWidget(
     appListData: State<AppListData<T>?>,
     listModel: AppListModel<T>,
+    header: @Composable () -> Unit,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
     bottomPadding: Dp,
 ) {
@@ -81,6 +91,10 @@
             state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
             contentPadding = PaddingValues(bottom = bottomPadding),
         ) {
+            item(contentType = CONTENT_TYPE_HEADER) {
+                header()
+            }
+
             items(count = list.size, key = { option to list[it].record.app.packageName }) {
                 val appEntry = list[it]
                 val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
@@ -94,19 +108,17 @@
 }
 
 @Composable
-private fun <T : AppRecord> loadAppEntries(
-    appListConfig: AppListConfig,
+private fun <T : AppRecord> loadAppListData(
+    config: AppListConfig,
     listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
+    state: AppListState,
 ): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
-    viewModel.appListConfig.setIfAbsent(appListConfig)
+    val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+    viewModel.appListConfig.setIfAbsent(config)
     viewModel.listModel.setIfAbsent(listModel)
-    viewModel.showSystem.Sync(showSystem)
-    viewModel.option.Sync(option)
-    viewModel.searchQuery.Sync(searchQuery)
+    viewModel.showSystem.Sync(state.showSystem)
+    viewModel.option.Sync(state.option)
+    viewModel.searchQuery.Sync(state.searchQuery)
 
     return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2953367..388a7d8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -37,6 +37,8 @@
 
 /**
  * The full screen template for an App List page.
+ *
+ * @param header the description header appears before all the applications.
  */
 @Composable
 fun <T : AppRecord> AppListPage(
@@ -44,6 +46,7 @@
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
+    header: @Composable () -> Unit = {},
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
     val showSystem = rememberSaveable { mutableStateOf(false) }
@@ -59,14 +62,17 @@
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
                 AppList(
-                    appListConfig = AppListConfig(
+                    config = AppListConfig(
                         userId = userInfo.id,
                         showInstantApps = showInstantApps,
                     ),
                     listModel = listModel,
-                    showSystem = showSystem,
-                    option = selectedOption,
-                    searchQuery = searchQuery,
+                    state = AppListState(
+                        showSystem = showSystem,
+                        option = selectedOption,
+                        searchQuery = searchQuery,
+                    ),
+                    header = header,
                     appItem = appItem,
                     bottomPadding = bottomPadding,
                 )
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
new file mode 100644
index 0000000..80c4eac
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
+import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun whenNoApps() {
+        setContent(appEntries = emptyList())
+
+        composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowAppItem() {
+        setContent(appEntries = listOf(APP_ENTRY))
+
+        composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowHeader() {
+        setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+
+        composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+    }
+
+    private fun setContent(
+        header: @Composable () -> Unit = {},
+        appEntries: List<AppEntry<TestAppRecord>>,
+    ) {
+        composeTestRule.setContent {
+            AppList(
+                config = AppListConfig(userId = USER_ID, showInstantApps = false),
+                listModel = TestAppListModel(),
+                state = AppListState(
+                    showSystem = false.toState(),
+                    option = 0.toState(),
+                    searchQuery = "".toState(),
+                ),
+                header = header,
+                appItem = { AppListItem(it) {} },
+                bottomPadding = 0.dp,
+                appListDataSupplier = {
+                    stateOf(AppListData(appEntries, option = 0))
+                }
+            )
+        }
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val HEADER = "Header"
+        val APP_ENTRY = AppEntry(
+            record = TestAppRecord(ApplicationInfo()),
+            label = "AAA",
+            labelCollationKey = CollationKey("", byteArrayOf()),
+        )
+    }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.asyncMapItem { TestAppRecord(it) }
+
+    @Composable
+    override fun getSummary(option: Int, record: TestAppRecord) = null
+
+    override fun filter(
+        userIdFlow: Flow<Int>,
+        option: Int,
+        recordListFlow: Flow<List<TestAppRecord>>,
+    ) = recordListFlow
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 6bc1160..dd56bde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -188,7 +188,6 @@
     /**
      * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
      * Hearing Aid Service is connected and the HiSyncId's are now available.
-     * @param LocalBluetoothProfileManager profileManager
      */
     public synchronized void updateHearingAidsDevices() {
         mHearingAidDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
new file mode 100644
index 0000000..f06aab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HapClientProfile handles the Bluetooth HAP service client role.
+ */
+public class HapClientProfile implements LocalBluetoothProfile {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            HearingAidType.TYPE_INVALID,
+            HearingAidType.TYPE_BINAURAL,
+            HearingAidType.TYPE_MONAURAL,
+            HearingAidType.TYPE_BANDED,
+            HearingAidType.TYPE_RFU
+    })
+
+    /** Hearing aid type definition for HAP Client. */
+    public @interface HearingAidType {
+        int TYPE_INVALID = -1;
+        int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL;
+        int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL;
+        int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED;
+        int TYPE_RFU = BluetoothHapClient.TYPE_RFU;
+    }
+
+    static final String NAME = "HapClient";
+    private static final String TAG = "HapClientProfile";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 1;
+
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+    private BluetoothHapClient mService;
+    private boolean mIsProfileReady;
+
+    // These callbacks run on the main thread.
+    private final class HapClientServiceListener implements BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothHapClient) proxy;
+            // We just bound to the service, so refresh the UI for any connected HapClient devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // Adds a new device into mDeviceManager if it does not exist
+                if (device == null) {
+                    Log.w(TAG, "HapClient profile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(nextDevice);
+                }
+                device.onProfileStateChanged(
+                        HapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mIsProfileReady = true;
+            mProfileManager.callServiceConnectedListeners();
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            mIsProfileReady = false;
+            mProfileManager.callServiceDisconnectedListeners();
+        }
+    }
+
+    HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+        if (bluetoothManager != null) {
+            mBluetoothAdapter = bluetoothManager.getAdapter();
+            mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(),
+                    BluetoothProfile.HAP_CLIENT);
+        } else {
+            mBluetoothAdapter = null;
+        }
+    }
+
+    /**
+     * Get hearing aid devices matching connection states{
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    /**
+     * Get hearing aid devices matching connection states{
+     * {@code BluetoothProfile.STATE_DISCONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectableDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    private List<BluetoothDevice> getDevicesByStates(int[] states) {
+        if (mService == null) {
+            return new ArrayList<>(0);
+        }
+        return mService.getDevicesMatchingConnectionStates(states);
+    }
+
+    /**
+     * Gets the hearing aid type of the device.
+     *
+     * @param device is the device for which we want to get the hearing aid type
+     * @return hearing aid type
+     */
+    @HearingAidType
+    public int getHearingAidType(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return HearingAidType.TYPE_INVALID;
+        }
+        return mService.getHearingAidType(device);
+    }
+
+    /**
+     * Gets if this device supports synchronized presets or not
+     *
+     * @param device is the device for which we want to know if supports synchronized presets
+     * @return {@code true} if the device supports synchronized presets
+     */
+    public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportSynchronizedPresets(device);
+    }
+
+    /**
+     * Gets if this device supports independent presets or not
+     *
+     * @param device is the device for which we want to know if supports independent presets
+     * @return {@code true} if the device supports independent presets
+     */
+    public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportIndependentPresets(device);
+    }
+
+    /**
+     * Gets if this device supports dynamic presets or not
+     *
+     * @param device is the device for which we want to know if supports dynamic presets
+     * @return {@code true} if the device supports dynamic presets
+     */
+    public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportDynamicPresets(device);
+    }
+
+    /**
+     * Gets if this device supports writable presets or not
+     *
+     * @param device is the device for which we want to know if supports writable presets
+     * @return {@code true} if the device supports writable presets
+     */
+    public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportWritablePresets(device);
+    }
+
+    @Override
+    public boolean accessProfileEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    @Override
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return false;
+        }
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return CONNECTION_POLICY_FORBIDDEN;
+        }
+        return mService.getConnectionPolicy(device);
+    }
+
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null || device == null) {
+            return false;
+        }
+        if (enabled) {
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+            }
+        } else {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
+    }
+
+    @Override
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    @Override
+    public int getProfileId() {
+        return BluetoothProfile.HAP_CLIENT;
+    }
+
+    @Override
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    @Override
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_hearing_aid;
+    }
+
+    @Override
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_hearing_aid_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_hearing_aid_profile_summary_connected;
+
+            default:
+                return BluetoothUtils.getConnectionStateSummary(state);
+        }
+    }
+
+    @Override
+    public int getDrawableResource(BluetoothClass btClass) {
+        return com.android.internal.R.drawable.ic_bt_hearing_aid;
+    }
+
+    /**
+     * Gets the name of this class
+     *
+     * @return the name of this class
+     */
+    public String toString() {
+        return NAME;
+    }
+
+    protected void finalize() {
+        Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService);
+                mService = null;
+            } catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HAP Client proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index 51cf59c..ac9cdac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -34,16 +34,16 @@
     private static final float CORNER_LINE_LENGTH = 264f;   // 264dp
     private static final float CORNER_RADIUS = 16f;         // 16dp
 
-    final private int mCornerColor;
-    final private int mFocusedCornerColor;
-    final private int mBackgroundColor;
+    private final int mCornerColor;
+    private final int mFocusedCornerColor;
+    private final int mBackgroundColor;
 
-    final private Paint mStrokePaint;
-    final private Paint mTransparentPaint;
-    final private Paint mBackgroundPaint;
+    private final Paint mStrokePaint;
+    private final Paint mTransparentPaint;
+    private final Paint mBackgroundPaint;
 
-    final private float mRadius;
-    final private float mInnerRidus;
+    private final float mRadius;
+    private final float mInnerRadius;
 
     private Bitmap mMaskBitmap;
     private Canvas mMaskCanvas;
@@ -72,7 +72,7 @@
         mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS,
                 getResources().getDisplayMetrics());
         // Inner radius needs to minus stroke width for keeping the width of border consistent.
-        mInnerRidus = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+        mInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                 CORNER_RADIUS - CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
 
         mCornerColor = context.getResources().getColor(R.color.qr_corner_line_color);
@@ -95,7 +95,10 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if(mMaskBitmap == null) {
+        if (!isLaidOut()) {
+            return;
+        }
+        if (mMaskBitmap == null) {
             mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
             mMaskCanvas = new Canvas(mMaskBitmap);
         }
@@ -105,16 +108,18 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        // Set frame line color.
-        mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
-        // Draw background color.
-        mMaskCanvas.drawColor(mBackgroundColor);
-        // Draw outer corner.
-        mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
-        // Draw inner transparent corner.
-        mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRidus, mInnerRidus, mTransparentPaint);
+        if (mMaskCanvas != null && mMaskBitmap != null) {
+            // Set frame line color.
+            mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
+            // Draw background color.
+            mMaskCanvas.drawColor(mBackgroundColor);
+            // Draw outer corner.
+            mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
+            // Draw inner transparent corner.
+            mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRadius, mInnerRadius, mTransparentPaint);
 
-        canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+            canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+        }
         super.onDraw(canvas);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
new file mode 100644
index 0000000..03a792a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class HapClientProfileTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private BluetoothHapClient mService;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private BluetoothProfile.ServiceListener mServiceListener;
+    private HapClientProfile mProfile;
+
+    @Before
+    public void setUp() {
+        mProfile = new HapClientProfile(mContext, mDeviceManager, mProfileManager);
+        final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        final ShadowBluetoothAdapter shadowBluetoothAdapter =
+                Shadow.extract(bluetoothManager.getAdapter());
+        mServiceListener = shadowBluetoothAdapter.getServiceListener();
+    }
+
+    @Test
+    public void onServiceConnected_isProfileReady() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        assertThat(mProfile.isProfileReady()).isTrue();
+        verify(mProfileManager).callServiceConnectedListeners();
+    }
+
+    @Test
+    public void onServiceDisconnected_isProfileNotReady() {
+        mServiceListener.onServiceDisconnected(BluetoothProfile.HAP_CLIENT);
+
+        assertThat(mProfile.isProfileReady()).isFalse();
+        verify(mProfileManager).callServiceDisconnectedListeners();
+    }
+
+    @Test
+    public void getConnectionStatus_returnCorrectConnectionState() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionState(mBluetoothDevice))
+                .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyForbidden_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+    }
+
+    @Test
+    public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+                .isEqualTo(CONNECTION_POLICY_ALLOWED);
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void getConnectedDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        int[] connectedStates = new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectedList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectedStates))
+                .thenReturn(connectedList);
+
+        assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+    }
+
+    @Test
+    public void getConnectableDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        int[] connectableStates = new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectableList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectableStates))
+                .thenReturn(connectableList);
+
+        assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
+    }
+}
diff --git a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
index e34f5c8..d866653 100644
--- a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
+++ b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
@@ -104,7 +104,7 @@
 
             final IntentFilter filter = new IntentFilter();
             filter.addAction(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
-            context.registerReceiver(receiver, filter);
+            context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         /**
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 753e110..d46a93c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -115,6 +115,7 @@
         "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
         "com.google.android.material_material",
+        "kotlin-reflect",
         "kotlinx_coroutines_android",
         "kotlinx_coroutines",
         "iconloader_base",
@@ -125,6 +126,7 @@
         "jsr330",
         "lottie",
         "LowLightDreamLib",
+        "motion_tool_lib",
     ],
     manifest: "AndroidManifest.xml",
 
@@ -223,6 +225,7 @@
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
         "mockito-target-extended-minus-junit4",
+        "androidx.test.ext.junit",
         "testables",
         "truth-prebuilt",
         "monet",
@@ -230,6 +233,7 @@
         "jsr330",
         "WindowManager-Shell",
         "LowLightDreamLib",
+        "motion_tool_lib",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..61df65a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -557,6 +557,16 @@
                   android:showForAllUsers="true">
         </activity>
 
+        <!-- started from SensoryPrivacyService -->
+        <activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
+            android:theme="@style/BottomSheet"
+            android:finishOnCloseSystemDialogs="true"
+            android:showForAllUsers="true">
+        </activity>
+
 
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbAccessoryUriActivity"
@@ -976,5 +986,11 @@
                 <action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
             </intent-filter>
         </receiver>
+
+        <activity android:name=".logcat.LogAccessDialogActivity"
+                  android:theme="@android:style/Theme.Translucent.NoTitleBar"
+                  android:excludeFromRecents="true"
+                  android:exported="false">
+        </activity>
     </application>
 </manifest>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index c50340c..e52a57f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -82,6 +82,18 @@
     boolean isFalseTap(@Penalty int penalty);
 
     /**
+     * Returns true if the FalsingManager thinks the last gesture was not a valid long tap.
+     *
+     * Use this method to validate a long tap for launching an action, like long press on a UMO
+     *
+     * The only parameter, penalty, indicates how much this should affect future gesture
+     * classifications if this long tap looks like a false.
+     * As long taps are hard to confirm as false or otherwise,
+     * a low penalty value is encouraged unless context indicates otherwise.
+     */
+    boolean isFalseLongTap(@Penalty int penalty);
+
+    /**
      * Returns true if the last two gestures do not look like a double tap.
      *
      * Only works on data that has already been reported to the FalsingManager. Be sure that
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 88d8f78f..569ee76 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -30,7 +30,7 @@
     </item>
     <item android:id="@android:id/progress"
           android:gravity="center_vertical|fill_horizontal">
-            <com.android.systemui.util.RoundedCornerProgressDrawable
+            <com.android.systemui.util.BrightnessProgressDrawable
                 android:drawable="@drawable/brightness_progress_full_drawable"
             />
     </item>
diff --git a/core/res/res/drawable/grant_permissions_buttons_bottom.xml b/packages/SystemUI/res/drawable/grant_permissions_buttons_bottom.xml
similarity index 100%
rename from core/res/res/drawable/grant_permissions_buttons_bottom.xml
rename to packages/SystemUI/res/drawable/grant_permissions_buttons_bottom.xml
diff --git a/core/res/res/drawable/grant_permissions_buttons_top.xml b/packages/SystemUI/res/drawable/grant_permissions_buttons_top.xml
similarity index 100%
rename from core/res/res/drawable/grant_permissions_buttons_top.xml
rename to packages/SystemUI/res/drawable/grant_permissions_buttons_top.xml
diff --git a/packages/SystemUI/res/drawable/ic_doc_document.xml b/packages/SystemUI/res/drawable/ic_doc_document.xml
new file mode 100644
index 0000000..df9ddab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_doc_document.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2022 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+            android:fillColor="#FF4883F3"
+            android:pathData="M19 3H5c-1.1 0,-2 .9,-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2,-.9 2,-2V5c0,-1.1,-.9,-2,-2,-2zm-1.99 6H7V7h10.01v2zm0 4H7v-2h10.01v2zm-3 4H7v-2h7.01v2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml b/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml
new file mode 100644
index 0000000..ae0f4b2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:insetTop="@dimen/dialog_button_vertical_inset"
+    android:insetBottom="@dimen/dialog_button_vertical_inset">
+    <ripple android:color="?android:attr/colorControlHighlight">
+        <item android:id="@android:id/mask">
+            <shape android:shape="rectangle">
+                <solid android:color="@android:color/white"/>
+                <corners android:radius="20dp"/>
+            </shape>
+        </item>
+        <item>
+            <shape android:shape="rectangle">
+                <corners android:radius="20dp"/>
+                <solid android:color="@android:color/transparent"/>
+                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                    android:width="1dp"
+                    />
+                <padding android:left="@dimen/dialog_button_horizontal_padding"
+                    android:top="@dimen/dialog_button_vertical_padding"
+                    android:right="@dimen/dialog_button_horizontal_padding"
+                    android:bottom="@dimen/dialog_button_vertical_padding"/>
+            </shape>
+        </item>
+    </ripple>
+</inset>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 006b260..9add32c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -18,6 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/dream_overlay_status_bar"
+    android:visibility="invisible"
     android:layout_width="match_parent"
     android:layout_height="@dimen/dream_overlay_status_bar_height"
     android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
diff --git a/core/res/res/layout/log_access_user_consent_dialog_permission.xml b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
similarity index 96%
rename from core/res/res/layout/log_access_user_consent_dialog_permission.xml
rename to packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
index 3da14c8..89e36ac 100644
--- a/core/res/res/layout/log_access_user_consent_dialog_permission.xml
+++ b/packages/SystemUI/res/layout/log_access_user_consent_dialog_permission.xml
@@ -75,7 +75,7 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:text="@string/log_access_confirmation_allow"
-                    style="@style/PermissionGrantButtonTop"
+                    style="?permissionGrantButtonTopStyle"
                     android:textAppearance="@style/PermissionGrantButtonTextAppearance"
                     android:layout_marginBottom="5dp"
                     android:layout_centerHorizontal="true"
@@ -89,7 +89,7 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:text="@string/log_access_confirmation_deny"
-                    style="@style/PermissionGrantButtonBottom"
+                    style="?permissionGrantButtonBottomStyle"
                     android:textAppearance="@style/PermissionGrantButtonTextAppearance"
                     android:layout_centerHorizontal="true"
                     android:layout_alignParentTop="true"
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index 78884ff..fa9d739 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -80,7 +80,7 @@
 
     <TextView
         android:id="@+id/add"
-        style="@style/Widget.Dialog.Button.BorderButton"
+        android:background="@drawable/user_switcher_fullscreen_button_bg"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json
new file mode 100644
index 0000000..2797996
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_portrait_base_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"biometricprompt_landscape_base","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}]}],"layers":[{"ddd":0,"ind":6,"ty":0,"nm":"biometricprompt_landscape_base","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[170,170,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":340,"h":340,"ip":0,"op":900,"st":0,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json
new file mode 100644
index 0000000..bf65b34
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_landscape_base","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json
new file mode 100644
index 0000000..7351d7c
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Portrait_Base_TopLeft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
new file mode 100644
index 0000000..aab914f
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+     such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+     to wide devices that are shorter in height, like foldables. -->
+<resources>
+    <!-- Space between status view and notification shelf -->
+    <dimen name="keyguard_status_view_bottom_margin">35dp</dimen>
+    <dimen name="keyguard_clock_top_margin">40dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 347cf29..d9df337 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,8 +17,6 @@
 <resources>
     <dimen name="notification_panel_margin_horizontal">48dp</dimen>
     <dimen name="status_view_margin_horizontal">62dp</dimen>
-    <dimen name="keyguard_clock_top_margin">40dp</dimen>
-    <dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
 
     <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
new file mode 100644
index 0000000..97ead01
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+     such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+     to wide devices that are shorter in height, like foldables. -->
+<resources>
+    <!-- Space between status view and notification shelf -->
+    <dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
+    <dimen name="keyguard_clock_top_margin">80dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 3d8da8a..17f82b5 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,8 +21,6 @@
      for different hardware and product builds. -->
 <resources>
     <dimen name="status_view_margin_horizontal">124dp</dimen>
-    <dimen name="keyguard_clock_top_margin">80dp</dimen>
-    <dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
 
     <dimen name="large_screen_shade_header_left_padding">24dp</dimen>
diff --git a/packages/SystemUI/res/values-television/strings.xml b/packages/SystemUI/res/values-television/strings.xml
new file mode 100644
index 0000000..f30b73e
--- /dev/null
+++ b/packages/SystemUI/res/values-television/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+    <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs.
+    </string>
+
+    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+    <string name="log_access_confirmation_learn_more" translatable="false"></string>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-television/styles.xml b/packages/SystemUI/res/values-television/styles.xml
index 12020f9..c517845 100644
--- a/packages/SystemUI/res/values-television/styles.xml
+++ b/packages/SystemUI/res/values-television/styles.xml
@@ -63,4 +63,10 @@
         <item name="android:paddingVertical">@dimen/bottom_sheet_button_padding_vertical</item>
         <item name="android:stateListAnimator">@anim/tv_bottom_sheet_button_state_list_animator</item>
     </style>
+
+    <!-- The style for log access consent button -->
+    <style name="LogAccessDialogTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+        <item name="permissionGrantButtonTopStyle">?android:buttonBarButtonStyle</item>
+        <item name="permissionGrantButtonBottomStyle">?android:buttonBarButtonStyle</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index df0659d..44ba3f6 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -204,5 +204,10 @@
         <attr name="passwordTextAppearance" format="reference" />
         <attr name="errorTextAppearance" format="reference"/>
     </declare-styleable>
+
+    <declare-styleable name="LogAccessPermissionGrantDialog">
+        <attr name="permissionGrantButtonTopStyle" format="reference"/>
+        <attr name="permissionGrantButtonBottomStyle" format="reference"/>
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb..ce9829b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,6 +743,17 @@
 
     <integer name="complicationRestoreMs">1000</integer>
 
+    <!-- Duration in milliseconds of the dream in un-blur animation. -->
+    <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
+    <!-- Delay in milliseconds of the dream in un-blur animation. -->
+    <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+    <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
+    <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
+    <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
+    <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
+    <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
+    <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+
     <!-- Icons that don't show in a collapsed non-keyguard statusbar -->
     <string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
         <item>@*android:string/status_bar_volume</item>
@@ -783,4 +794,8 @@
         <item>@color/dream_overlay_aqi_very_unhealthy</item>
         <item>@color/dream_overlay_aqi_hazardous</item>
     </integer-array>
+
+    <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
+         is available. If false, UI will never show regardless of tethering availability" -->
+    <bool name="config_show_wifi_tethering">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f02f29a..ad45471 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -407,7 +407,7 @@
     <dimen name="match_parent">-1px</dimen>
 
     <!-- Height of status bar in split shade mode - visible only on large screens -->
-    <dimen name="large_screen_shade_header_height">@*android:dimen/quick_qs_offset_height</dimen>
+    <dimen name="large_screen_shade_header_height">48dp</dimen>
     <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
     <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
 
@@ -762,7 +762,7 @@
     <dimen name="keyguard_lock_padding">20dp</dimen>
 
     <dimen name="keyguard_indication_margin_bottom">32dp</dimen>
-    <dimen name="lock_icon_margin_bottom">110dp</dimen>
+    <dimen name="lock_icon_margin_bottom">74dp</dimen>
     <dimen name="ambient_indication_margin_bottom">71dp</dimen>
 
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 4fd25a9..2b6ab30 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -195,5 +195,7 @@
     <item type="id" name="pm_lite"/>
     <item type="id" name="settings_button_container"/>
 
+    <item type="id" name="log_access_dialog_allow_button" />
+    <item type="id" name="log_access_dialog_deny_button" />
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b325c56..cfe5681 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -142,7 +142,7 @@
     <string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
 
     <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
-    <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string>
+    <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to an admin user.</string>
 
     <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] -->
     <string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string>
@@ -172,7 +172,7 @@
     <string name="wifi_debugging_secondary_user_title">Wireless debugging not allowed</string>
 
     <!-- Message of notification shown when trying to enable wireless debugging but a secondary user is the current foreground user. [CHAR LIMIT=NONE] -->
-    <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to the primary user.</string>
+    <string name="wifi_debugging_secondary_user_message">The user currently signed in to this device can\u2019t turn on wireless debugging. To use this feature, switch to an admin user.</string>
 
     <!-- Title of USB contaminant presence dialog [CHAR LIMIT=NONE] -->
     <string name="usb_contaminant_title">USB port disabled</string>
@@ -403,6 +403,8 @@
     <string name="keyguard_face_failed">Can\u2019t recognize face</string>
     <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
     <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
+    <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] -->
+    <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
 
     <!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
@@ -783,6 +785,62 @@
         Microphone and camera available
     </string>
 
+    <!--- Title of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_turned_on_dialog_title">
+        Microphone turned on
+    </string>
+
+    <!--- Title of dialog triggered if the microphone sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_turned_off_dialog_title">
+        Microphone turned off
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_unblocked_dialog_content">
+        Microphone is enabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are no active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content">
+        Microphone access is disabled for all apps and services.
+        You can enable microphone access in Settings &gt; Privacy &gt; Microphone.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content">
+        Microphone access is disabled for all apps and services.
+        You can change this in Settings &gt; Privacy &gt; Microphone.
+    </string>
+
+    <!--- Title of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_turned_on_dialog_title">
+        Camera turned on
+    </string>
+
+    <!--- Title of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_turned_off_dialog_title">
+        Camera turned off
+    </string>
+
+    <!--- Content of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_unblocked_dialog_content">
+        Camera is enabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_blocked_dialog_content">
+        Camera access is disabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
+
+    <!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
+    <string name="sensor_privacy_dialog_open_settings">Open settings.</string>
+
     <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
     <string name="media_seamless_other_device">Other device</string>
 
@@ -989,6 +1047,21 @@
     <!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
     <string name="media_projection_permission_app_selector_title">Share or record an app</string>
 
+    <!-- Media projection permission dialog title when there is no app name (e.g. it could be a system service when casting). [CHAR LIMIT=100] -->
+    <string name="media_projection_permission_dialog_system_service_title">Allow this app to share or record?</string>
+
+    <!-- Media projection permission warning for capturing the whole screen when a system service requests it (e.g. when casting). [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen">When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Media projection permission warning for capturing a single app when a system service requests it (e.g. when casting). [CHAR LIMIT=350] -->
+    <string name="media_projection_permission_dialog_system_service_warning_single_app">When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+    <!-- Title for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=100] -->
+    <string name="screen_capturing_disabled_by_policy_dialog_title">Blocked by your IT admin</string>
+
+    <!-- Description for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=350] -->
+    <string name="screen_capturing_disabled_by_policy_dialog_description">Screen capturing is disabled by device policy</string>
+
     <!-- The text to clear all notifications. [CHAR LIMIT=60] -->
     <string name="clear_all_notifications_text">Clear all</string>
 
@@ -2697,4 +2770,19 @@
 
     <!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
     <string name="dream_time_complication_24_hr_time_format">kk:mm</string>
+
+    <!-- Title for the log access confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="log_access_confirmation_title">Allow <xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> to access all device logs?</string>
+    <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=40] -->
+    <string name="log_access_confirmation_allow">Allow one-time access</string>
+    <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
+    <string name="log_access_confirmation_deny">Don\u2019t allow</string>
+
+    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
+    <string name="log_access_confirmation_body">Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs.
+        \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.
+    </string>
+
+    <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
+    <string name="log_access_confirmation_learn_more" translatable="false">&lt;a href="https://support.google.com/android?p=system_logs#topic=7313011"&gt;Learn more&lt;/a&gt;</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index e76887b..4e4bfe2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1274,4 +1274,45 @@
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
     </style>
+
+
+    <!-- The style for log access consent dialog -->
+    <style name="LogAccessDialogTheme" parent="@style/Theme.SystemUI.Dialog.Alert">
+        <item name="permissionGrantButtonTopStyle">@style/PermissionGrantButtonTop</item>
+        <item name="permissionGrantButtonBottomStyle">@style/PermissionGrantButtonBottom</item>
+    </style>
+
+    <style name="AllowLogAccess">
+        <item name="android:textSize">24sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="PrimaryAllowLogAccess">
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="PermissionGrantButtonTextAppearance">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@android:color/system_neutral1_900</item>
+    </style>
+
+    <style name="PermissionGrantButtonTop"
+           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:layout_width">332dp</item>
+        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_marginTop">2dp</item>
+        <item name="android:layout_marginBottom">2dp</item>
+        <item name="android:background">@drawable/grant_permissions_buttons_top</item>
+    </style>
+
+    <style name="PermissionGrantButtonBottom"
+           parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored">
+        <item name="android:layout_width">332dp</item>
+        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_marginTop">2dp</item>
+        <item name="android:layout_marginBottom">2dp</item>
+        <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cdbf8ab..06d425c 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -107,7 +107,7 @@
         android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="0dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="@id/date"
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 88b4f43..af4be1a 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -98,7 +98,7 @@
         android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="0dp"
+            android:layout_height="@dimen/large_screen_shade_header_min_height"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintEnd_toEndOf="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index ffaeaaa..485a0d3 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -52,7 +52,11 @@
         "SystemUIUnfoldLib",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx.concurrent_concurrent-futures",
-        "gson",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-ktx",
+        "androidx.recyclerview_recyclerview",
+        "kotlinx_coroutines_android",
+        "kotlinx_coroutines",
         "dagger2",
         "jsr330",
     ],
@@ -64,6 +68,7 @@
     },
     min_sdk_version: "current",
     plugins: ["dagger2-compiler"],
+    kotlincflags: ["-Xjvm-default=enable"],
 }
 
 java_library {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index f7049cf..933e586 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -89,7 +89,7 @@
  *
  * It can be changed or overridden in debug builds but not in release builds.
  */
-data class UnreleasedFlag @JvmOverloads constructor(
+data class UnreleasedFlag constructor(
     override val id: Int,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -100,7 +100,7 @@
  *
  * It can be changed or overridden in any build, meaning it can be turned off if needed.
  */
-data class ReleasedFlag @JvmOverloads constructor(
+data class ReleasedFlag constructor(
     override val id: Int,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
@@ -111,7 +111,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class ResourceBooleanFlag @JvmOverloads constructor(
+data class ResourceBooleanFlag constructor(
     override val id: Int,
     @BoolRes override val resourceId: Int,
     override val teamfood: Boolean = false
@@ -124,7 +124,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class DeviceConfigBooleanFlag @JvmOverloads constructor(
+data class DeviceConfigBooleanFlag constructor(
     override val id: Int,
     override val name: String,
     override val namespace: String,
@@ -139,7 +139,7 @@
  *
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
-data class SysPropBooleanFlag @JvmOverloads constructor(
+data class SysPropBooleanFlag constructor(
     override val id: Int,
     override val name: String,
     override val default: Boolean = false
@@ -148,7 +148,7 @@
     override val teamfood: Boolean = false
 }
 
-data class StringFlag @JvmOverloads constructor(
+data class StringFlag constructor(
     override val id: Int,
     override val default: String = "",
     override val teamfood: Boolean = false,
@@ -173,13 +173,13 @@
     }
 }
 
-data class ResourceStringFlag @JvmOverloads constructor(
+data class ResourceStringFlag constructor(
     override val id: Int,
     @StringRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<String>
 
-data class IntFlag @JvmOverloads constructor(
+data class IntFlag constructor(
     override val id: Int,
     override val default: Int = 0,
     override val teamfood: Boolean = false,
@@ -205,13 +205,13 @@
     }
 }
 
-data class ResourceIntFlag @JvmOverloads constructor(
+data class ResourceIntFlag constructor(
     override val id: Int,
     @IntegerRes override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<Int>
 
-data class LongFlag @JvmOverloads constructor(
+data class LongFlag constructor(
     override val id: Int,
     override val default: Long = 0,
     override val teamfood: Boolean = false,
@@ -237,7 +237,7 @@
     }
 }
 
-data class FloatFlag @JvmOverloads constructor(
+data class FloatFlag constructor(
     override val id: Int,
     override val default: Float = 0f,
     override val teamfood: Boolean = false,
@@ -263,13 +263,13 @@
     }
 }
 
-data class ResourceFloatFlag @JvmOverloads constructor(
+data class ResourceFloatFlag constructor(
     override val id: Int,
     override val resourceId: Int,
     override val teamfood: Boolean = false
 ) : ResourceFlag<Int>
 
-data class DoubleFlag @JvmOverloads constructor(
+data class DoubleFlag constructor(
     override val id: Int,
     override val default: Double = 0.0,
     override val teamfood: Boolean = false,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
index e9ea19d..eeb6031 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
@@ -24,6 +24,7 @@
 private const val FIELD_TYPE = "type"
 private const val TYPE_BOOLEAN = "boolean"
 private const val TYPE_STRING = "string"
+private const val TYPE_INT = "int"
 
 private const val TAG = "FlagSerializer"
 
@@ -77,4 +78,10 @@
     JSONObject::getString
 )
 
+object IntFlagSerializer : FlagSerializer<Int>(
+    TYPE_INT,
+    JSONObject::put,
+    JSONObject::getInt
+)
+
 class InvalidFlagStorageException : Exception("Data found but is invalid")
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 8aa3aba..a14f971 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -291,8 +291,10 @@
     }
 
     private void endAnimations(String reason, boolean cancel) {
-        Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
-        Trace.endSection();
+        if (Trace.isEnabled()) {
+            Trace.instant(Trace.TRACE_TAG_APP,
+                    "KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+        }
         mVisible = false;
         mTmpArray.addAll(mRunningAnimations);
         int size = mTmpArray.size();
@@ -502,20 +504,23 @@
 
         @Override
         public void onAnimationStart(Animator animation) {
-            Trace.beginSection("KeyButtonRipple.start." + mName);
-            Trace.endSection();
+            if (Trace.isEnabled()) {
+                Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.start." + mName);
+            }
         }
 
         @Override
         public void onAnimationCancel(Animator animation) {
-            Trace.beginSection("KeyButtonRipple.cancel." + mName);
-            Trace.endSection();
+            if (Trace.isEnabled()) {
+                Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.cancel." + mName);
+            }
         }
 
         @Override
         public void onAnimationEnd(Animator animation) {
-            Trace.beginSection("KeyButtonRipple.end." + mName);
-            Trace.endSection();
+            if (Trace.isEnabled()) {
+                Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.end." + mName);
+            }
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 48821e8..601cb66 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.plugins.ClockProviderPlugin
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.shared.plugins.PluginManager
-import com.google.gson.Gson
+import org.json.JSONObject
 
 private val TAG = ClockRegistry::class.simpleName
 private const val DEBUG = true
@@ -47,7 +47,6 @@
         fun onClockChanged()
     }
 
-    private val gson = Gson()
     private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver = object : ContentObserver(handler) {
@@ -70,7 +69,7 @@
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
                 )
-                gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
+                ClockSetting.deserialize(json)?.clockId ?: DEFAULT_CLOCK_ID
             } catch (ex: Exception) {
                 Log.e(TAG, "Failed to parse clock setting", ex)
                 DEFAULT_CLOCK_ID
@@ -78,7 +77,7 @@
         }
         set(value) {
             try {
-                val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+                val json = ClockSetting.serialize(ClockSetting(value, System.currentTimeMillis()))
                 Settings.Secure.putString(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
@@ -198,8 +197,27 @@
     )
 
     @Keep
-    private data class ClockSetting(
+    data class ClockSetting(
         val clockId: ClockId,
         val _applied_timestamp: Long?
-    )
+    ) {
+        companion object {
+            private val KEY_CLOCK_ID = "clockId"
+            private val KEY_TIMESTAMP = "_applied_timestamp"
+
+            fun serialize(setting: ClockSetting): String {
+                return JSONObject()
+                    .put(KEY_CLOCK_ID, setting.clockId)
+                    .put(KEY_TIMESTAMP, setting._applied_timestamp)
+                    .toString()
+            }
+
+            fun deserialize(jsonStr: String): ClockSetting {
+                val json = JSONObject(jsonStr)
+                return ClockSetting(
+                    json.getString(KEY_CLOCK_ID),
+                    if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 3961438..ca780c8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -188,13 +188,10 @@
         dozeFraction: Float,
         foldFraction: Float,
     ) : ClockAnimations {
-        private var foldState = AnimationState(0f)
-        private var dozeState = AnimationState(0f)
+        private val dozeState = AnimationState(dozeFraction)
+        private val foldState = AnimationState(foldFraction)
 
         init {
-            dozeState = AnimationState(dozeFraction)
-            foldState = AnimationState(foldFraction)
-
             if (foldState.isActive) {
                 clocks.forEach { it.animateFoldAppear(false) }
             } else {
@@ -235,7 +232,7 @@
     private class AnimationState(
         var fraction: Float,
     ) {
-        var isActive: Boolean = fraction < 0.5f
+        var isActive: Boolean = fraction > 0.5f
         fun update(newFraction: Float): Pair<Boolean, Boolean> {
             if (newFraction == fraction) {
                 return Pair(isActive, false)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 65%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
index f79ca10..2dc7a28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
@@ -12,17 +12,16 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.wm.shell.floating;
-
-import android.content.Intent;
+package com.android.systemui.shared.keyguard.shared.model
 
 /**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Collection of all supported "slots", placements where keyguard quick affordances can appear on
+ * the lock screen.
  */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
+object KeyguardQuickAffordanceSlots {
+    const val SLOT_ID_BOTTOM_START = "bottom_start"
+    const val SLOT_ID_BOTTOM_END = "bottom_end"
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index bfbe88c..abefeba 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -60,11 +60,6 @@
     void onSystemUiStateChanged(int stateFlags) = 16;
 
     /**
-     * Sent when the split screen is resized
-     */
-    void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
-
-    /**
      * Sent when suggested rotation button could be shown
      */
     void onRotationProposal(int rotation, boolean isValid) = 18;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index b99b72b..1c532fe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -24,7 +24,6 @@
 import android.view.MotionEvent;
 
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
 
 /**
  * Temporary callbacks into SystemUI.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
deleted file mode 100644
index 37e706a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TransitionOldType;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
-import android.annotation.SuppressLint;
-import android.app.IApplicationThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-
-import com.android.wm.shell.util.CounterRotator;
-
-/**
- * @see RemoteAnimationAdapter
- */
-public class RemoteAnimationAdapterCompat {
-
-    private final RemoteAnimationAdapter mWrapped;
-    private final RemoteTransitionCompat mRemoteTransition;
-
-    public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
-            long statusBarTransitionDelay, IApplicationThread appThread) {
-        mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
-                statusBarTransitionDelay);
-        mRemoteTransition = buildRemoteTransition(runner, appThread);
-    }
-
-    public RemoteAnimationAdapter getWrapped() {
-        return mWrapped;
-    }
-
-    /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
-    public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
-            IApplicationThread appThread) {
-        return new RemoteTransitionCompat(
-                new RemoteTransition(wrapRemoteTransition(runner), appThread));
-    }
-
-    public RemoteTransitionCompat getRemoteTransition() {
-        return mRemoteTransition;
-    }
-
-    /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
-    public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
-            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
-        return new IRemoteAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(@TransitionOldType int transit,
-                    RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers,
-                    RemoteAnimationTarget[] nonApps,
-                    final IRemoteAnimationFinishedCallback finishedCallback) {
-                final Runnable animationFinishedCallback = new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            finishedCallback.onAnimationFinished();
-                        } catch (RemoteException e) {
-                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
-                                    + " finished callback", e);
-                        }
-                    }
-                };
-                remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
-                        nonApps, animationFinishedCallback);
-            }
-
-            @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
-                remoteAnimationAdapter.onAnimationCancelled();
-            }
-        };
-    }
-
-    private static IRemoteTransition.Stub wrapRemoteTransition(
-            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
-        return new IRemoteTransition.Stub() {
-            final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
-
-            @Override
-            public void startAnimation(IBinder token, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishCallback) {
-                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
-                final RemoteAnimationTarget[] apps =
-                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
-                final RemoteAnimationTarget[] wallpapers =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, true /* wallpapers */, t, leashMap);
-                final RemoteAnimationTarget[] nonApps =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, false /* wallpapers */, t, leashMap);
-
-                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
-                boolean isReturnToHome = false;
-                TransitionInfo.Change launcherTask = null;
-                TransitionInfo.Change wallpaper = null;
-                int launcherLayer = 0;
-                int rotateDelta = 0;
-                float displayW = 0;
-                float displayH = 0;
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    final TransitionInfo.Change change = info.getChanges().get(i);
-                    // skip changes that we didn't wrap
-                    if (!leashMap.containsKey(change.getLeash())) continue;
-                    if (change.getTaskInfo() != null
-                            && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
-                        isReturnToHome = change.getMode() == TRANSIT_OPEN
-                                || change.getMode() == TRANSIT_TO_FRONT;
-                        launcherTask = change;
-                        launcherLayer = info.getChanges().size() - i;
-                    } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
-                        wallpaper = change;
-                    }
-                    if (change.getParent() == null && change.getEndRotation() >= 0
-                            && change.getEndRotation() != change.getStartRotation()) {
-                        rotateDelta = change.getEndRotation() - change.getStartRotation();
-                        displayW = change.getEndAbsBounds().width();
-                        displayH = change.getEndAbsBounds().height();
-                    }
-                }
-
-                // Prepare for rotation if there is one
-                final CounterRotator counterLauncher = new CounterRotator();
-                final CounterRotator counterWallpaper = new CounterRotator();
-                if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
-                    counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
-                            rotateDelta, displayW, displayH);
-                    if (counterLauncher.getSurface() != null) {
-                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
-                    }
-                }
-
-                if (isReturnToHome) {
-                    if (counterLauncher.getSurface() != null) {
-                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
-                    }
-                    // Need to "boost" the closing things since that's what launcher expects.
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        final TransitionInfo.Change change = info.getChanges().get(i);
-                        final SurfaceControl leash = leashMap.get(change.getLeash());
-                        // skip changes that we didn't wrap
-                        if (leash == null) continue;
-                        final int mode = info.getChanges().get(i).getMode();
-                        // Only deal with independent layers
-                        if (!TransitionInfo.isIndependent(change, info)) continue;
-                        if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
-                            t.setLayer(leash, info.getChanges().size() * 3 - i);
-                            counterLauncher.addChild(t, leash);
-                        }
-                    }
-                    // Make wallpaper visible immediately since launcher apparently won't do this.
-                    for (int i = wallpapers.length - 1; i >= 0; --i) {
-                        t.show(wallpapers[i].leash);
-                        t.setAlpha(wallpapers[i].leash, 1.f);
-                    }
-                } else {
-                    if (launcherTask != null) {
-                        counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
-                    }
-                    if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
-                        counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
-                                rotateDelta, displayW, displayH);
-                        if (counterWallpaper.getSurface() != null) {
-                            t.setLayer(counterWallpaper.getSurface(), -1);
-                            counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
-                        }
-                    }
-                }
-                t.apply();
-
-                final Runnable animationFinishedCallback = new Runnable() {
-                    @Override
-                    @SuppressLint("NewApi")
-                    public void run() {
-                        final SurfaceControl.Transaction finishTransaction =
-                                new SurfaceControl.Transaction();
-                        counterLauncher.cleanUp(finishTransaction);
-                        counterWallpaper.cleanUp(finishTransaction);
-                        // Release surface references now. This is apparently to free GPU memory
-                        // while doing quick operations (eg. during CTS).
-                        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                            info.getChanges().get(i).getLeash().release();
-                        }
-                        // Don't release here since launcher might still be using them. Instead
-                        // let launcher release them (eg. via RemoteAnimationTargets)
-                        leashMap.clear();
-                        try {
-                            finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
-                        } catch (RemoteException e) {
-                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
-                                    + " finished callback", e);
-                        }
-                    }
-                };
-                synchronized (mFinishRunnables) {
-                    mFinishRunnables.put(token, animationFinishedCallback);
-                }
-                // TODO(bc-unlcok): Pass correct transit type.
-                remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
-                        apps, wallpapers, nonApps, () -> {
-                            synchronized (mFinishRunnables) {
-                                if (mFinishRunnables.remove(token) == null) return;
-                            }
-                            animationFinishedCallback.run();
-                        });
-            }
-
-            @Override
-            public void mergeAnimation(IBinder token, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishCallback) {
-                // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
-                //       to legacy cancel.
-                final Runnable finishRunnable;
-                synchronized (mFinishRunnables) {
-                    finishRunnable = mFinishRunnables.remove(mergeTarget);
-                }
-                if (finishRunnable == null) return;
-                remoteAnimationAdapter.onAnimationCancelled();
-                finishRunnable.run();
-            }
-        };
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
deleted file mode 100644
index ab55037..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.view.RemoteAnimationDefinition;
-
-/**
- * @see RemoteAnimationDefinition
- */
-public class RemoteAnimationDefinitionCompat {
-
-    private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition();
-
-    public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) {
-        mWrapped.addRemoteAnimation(transition, adapter.getWrapped());
-    }
-
-    public void addRemoteAnimation(int transition, int activityTypeFilter,
-            RemoteAnimationAdapterCompat adapter) {
-        mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped());
-    }
-
-    public RemoteAnimationDefinition getWrapped() {
-        return mWrapped;
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5809c81..93c8073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,12 +16,197 @@
 
 package com.android.systemui.shared.system;
 
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(@WindowManager.TransitionOldType int transit,
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.TransitionOldType;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.util.CounterRotator;
+
+public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
+
+    public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
-    void onAnimationCancelled();
+
+    @Override
+    public final void onAnimationStart(@TransitionOldType int transit,
+            RemoteAnimationTarget[] apps,
+            RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps,
+            final IRemoteAnimationFinishedCallback finishedCallback) {
+
+        onAnimationStart(transit, apps, wallpapers,
+                nonApps, () -> {
+                    try {
+                        finishedCallback.onAnimationFinished();
+                    } catch (RemoteException e) {
+                        Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+                                + " finished callback", e);
+                    }
+                });
+    }
+
+    public IRemoteTransition toRemoteTransition() {
+        return new IRemoteTransition.Stub() {
+            final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
+            @Override
+            public void startAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t,
+                    IRemoteTransitionFinishedCallback finishCallback) {
+                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
+                final RemoteAnimationTarget[] apps =
+                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
+                final RemoteAnimationTarget[] wallpapers =
+                        RemoteAnimationTargetCompat.wrapNonApps(
+                                info, true /* wallpapers */, t, leashMap);
+                final RemoteAnimationTarget[] nonApps =
+                        RemoteAnimationTargetCompat.wrapNonApps(
+                                info, false /* wallpapers */, t, leashMap);
+
+                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+                boolean isReturnToHome = false;
+                TransitionInfo.Change launcherTask = null;
+                TransitionInfo.Change wallpaper = null;
+                int launcherLayer = 0;
+                int rotateDelta = 0;
+                float displayW = 0;
+                float displayH = 0;
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    final TransitionInfo.Change change = info.getChanges().get(i);
+                    // skip changes that we didn't wrap
+                    if (!leashMap.containsKey(change.getLeash())) continue;
+                    if (change.getTaskInfo() != null
+                            && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+                        isReturnToHome = change.getMode() == TRANSIT_OPEN
+                                || change.getMode() == TRANSIT_TO_FRONT;
+                        launcherTask = change;
+                        launcherLayer = info.getChanges().size() - i;
+                    } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+                        wallpaper = change;
+                    }
+                    if (change.getParent() == null && change.getEndRotation() >= 0
+                            && change.getEndRotation() != change.getStartRotation()) {
+                        rotateDelta = change.getEndRotation() - change.getStartRotation();
+                        displayW = change.getEndAbsBounds().width();
+                        displayH = change.getEndAbsBounds().height();
+                    }
+                }
+
+                // Prepare for rotation if there is one
+                final CounterRotator counterLauncher = new CounterRotator();
+                final CounterRotator counterWallpaper = new CounterRotator();
+                if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+                    counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+                            rotateDelta, displayW, displayH);
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
+                    }
+                }
+
+                if (isReturnToHome) {
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
+                    }
+                    // Need to "boost" the closing things since that's what launcher expects.
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        final TransitionInfo.Change change = info.getChanges().get(i);
+                        final SurfaceControl leash = leashMap.get(change.getLeash());
+                        // skip changes that we didn't wrap
+                        if (leash == null) continue;
+                        final int mode = info.getChanges().get(i).getMode();
+                        // Only deal with independent layers
+                        if (!TransitionInfo.isIndependent(change, info)) continue;
+                        if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+                            t.setLayer(leash, info.getChanges().size() * 3 - i);
+                            counterLauncher.addChild(t, leash);
+                        }
+                    }
+                    // Make wallpaper visible immediately since launcher apparently won't do this.
+                    for (int i = wallpapers.length - 1; i >= 0; --i) {
+                        t.show(wallpapers[i].leash);
+                        t.setAlpha(wallpapers[i].leash, 1.f);
+                    }
+                } else {
+                    if (launcherTask != null) {
+                        counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
+                    }
+                    if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+                        counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+                                rotateDelta, displayW, displayH);
+                        if (counterWallpaper.getSurface() != null) {
+                            t.setLayer(counterWallpaper.getSurface(), -1);
+                            counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
+                        }
+                    }
+                }
+                t.apply();
+
+                final Runnable animationFinishedCallback = () -> {
+                    final SurfaceControl.Transaction finishTransaction =
+                            new SurfaceControl.Transaction();
+                    counterLauncher.cleanUp(finishTransaction);
+                    counterWallpaper.cleanUp(finishTransaction);
+                    // Release surface references now. This is apparently to free GPU memory
+                    // while doing quick operations (eg. during CTS).
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        info.getChanges().get(i).getLeash().release();
+                    }
+                    // Don't release here since launcher might still be using them. Instead
+                    // let launcher release them (eg. via RemoteAnimationTargets)
+                    leashMap.clear();
+                    try {
+                        finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+                    } catch (RemoteException e) {
+                        Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+                                + " finished callback", e);
+                    }
+                };
+                synchronized (mFinishRunnables) {
+                    mFinishRunnables.put(token, animationFinishedCallback);
+                }
+                // TODO(bc-unlcok): Pass correct transit type.
+                onAnimationStart(TRANSIT_OLD_NONE,
+                        apps, wallpapers, nonApps, () -> {
+                            synchronized (mFinishRunnables) {
+                                if (mFinishRunnables.remove(token) == null) return;
+                            }
+                            animationFinishedCallback.run();
+                        });
+            }
+
+            @Override
+            public void mergeAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t, IBinder mergeTarget,
+                    IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+                // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+                //       to legacy cancel.
+                final Runnable finishRunnable;
+                synchronized (mFinishRunnables) {
+                    finishRunnable = mFinishRunnables.remove(mergeTarget);
+                }
+                if (finishRunnable == null) return;
+                onAnimationCancelled(false /* isKeyguardOccluded */);
+                finishRunnable.run();
+            }
+        };
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d6655a7..d4d3d25 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -18,29 +18,22 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
 
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
-import android.content.ComponentName;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -53,72 +46,23 @@
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.RemoteTransition;
 import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
 
 /**
- * Wrapper to expose RemoteTransition (shell transitions) to Launcher.
- *
- * @see IRemoteTransition
- * @see TransitionFilter
+ * Helper class to build {@link RemoteTransition} objects
  */
-@DataClass
-public class RemoteTransitionCompat implements Parcelable {
+public class RemoteTransitionCompat {
     private static final String TAG = "RemoteTransitionCompat";
 
-    @NonNull final RemoteTransition mTransition;
-    @Nullable TransitionFilter mFilter = null;
-
-    RemoteTransitionCompat(RemoteTransition transition) {
-        mTransition = transition;
-    }
-
-    public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
-            @NonNull Executor executor, @Nullable IApplicationThread appThread) {
-        IRemoteTransition remote = new IRemoteTransition.Stub() {
-            @Override
-            public void startAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                final Runnable finishAdapter = () ->  {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to call transition finished callback", e);
-                    }
-                };
-                executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
-            }
-
-            @Override
-            public void mergeAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                final Runnable finishAdapter = () ->  {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to call transition finished callback", e);
-                    }
-                };
-                executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
-                        finishAdapter));
-            }
-        };
-        mTransition = new RemoteTransition(remote, appThread);
-    }
-
     /** Constructor specifically for recents animation */
-    public RemoteTransitionCompat(RecentsAnimationListener recents,
+    public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
             RecentsAnimationControllerCompat controller, IApplicationThread appThread) {
         IRemoteTransition remote = new IRemoteTransition.Stub() {
             final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
@@ -193,25 +137,7 @@
                 mRecentsSession.commitTasksAppearedIfNeeded(recents);
             }
         };
-        mTransition = new RemoteTransition(remote, appThread);
-    }
-
-    /** Adds a filter check that restricts this remote transition to home open transitions. */
-    public void addHomeOpenCheck(ComponentName homeActivity) {
-        if (mFilter == null) {
-            mFilter = new TransitionFilter();
-        }
-        // No need to handle the transition that also dismisses keyguard.
-        mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-        mFilter.mRequirements =
-                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
-                        new TransitionFilter.Requirement()};
-        mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
-        mFilter.mRequirements[0].mTopActivity = homeActivity;
-        mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
-        mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
-        mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
-        mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+        return new RemoteTransition(remote, appThread);
     }
 
     /**
@@ -505,161 +431,4 @@
         @Override public void animateNavigationBarToApp(long duration) {
         }
     }
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @DataClass.Generated.Member
-    /* package-private */ RemoteTransitionCompat(
-            @NonNull RemoteTransition transition,
-            @Nullable TransitionFilter filter) {
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull RemoteTransition getTransition() {
-        return mTransition;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable TransitionFilter getFilter() {
-        return mFilter;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mFilter != null) flg |= 0x2;
-        dest.writeByte(flg);
-        dest.writeTypedObject(mTransition, flags);
-        if (mFilter != null) dest.writeTypedObject(mFilter, flags);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
-        TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
-
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
-            = new Parcelable.Creator<RemoteTransitionCompat>() {
-        @Override
-        public RemoteTransitionCompat[] newArray(int size) {
-            return new RemoteTransitionCompat[size];
-        }
-
-        @Override
-        public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
-            return new RemoteTransitionCompat(in);
-        }
-    };
-
-    /**
-     * A builder for {@link RemoteTransitionCompat}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static class Builder {
-
-        private @NonNull RemoteTransition mTransition;
-        private @Nullable TransitionFilter mFilter;
-
-        private long mBuilderFieldsSet = 0L;
-
-        public Builder(
-                @NonNull RemoteTransition transition) {
-            mTransition = transition;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTransition);
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mTransition = value;
-            return this;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mFilter = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull RemoteTransitionCompat build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x2) == 0) {
-                mFilter = null;
-            }
-            RemoteTransitionCompat o = new RemoteTransitionCompat(
-                    mTransition,
-                    mFilter);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x4) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
-    @DataClass.Generated(
-            time = 1629321609807L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
-            inputSignatures = "private static final  java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic  void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate  com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate  android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate  android.window.WindowContainerToken mPausingTask\nprivate  android.window.WindowContainerToken mPipTask\nprivate  android.window.TransitionInfo mInfo\nprivate  android.view.SurfaceControl mOpeningLeash\nprivate  android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate  android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate  android.os.IBinder mTransition\n  void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
deleted file mode 100644
index accc456..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-/** Interface for something that runs a remote transition animation. */
-public interface RemoteTransitionRunner {
-    /**
-     * Starts a transition animation. Once complete, the implementation should call
-     * `finishCallback`.
-     */
-    void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
-            Runnable finishCallback);
-
-    /**
-     * Attempts to merge a transition into the currently-running animation. If merge is not
-     * possible/supported, this should do nothing. Otherwise, the implementation should call
-     * `finishCallback` immediately to indicate that it merged the transition.
-     *
-     * @param transition The transition that wants to be merged into the running animation.
-     * @param mergeTarget The transition to merge into (that this runner is currently animating).
-     */
-    default void mergeAnimation(IBinder transition, TransitionInfo info,
-            SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index d48d7ff..c9b8712 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -228,7 +228,7 @@
                 listenForDozing(this)
                 if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
                     listenForDozeAmountTransition(this)
-                    listenForGoneToAodTransition(this)
+                    listenForAnyStateToAodTransition(this)
                 } else {
                     listenForDozeAmount(this)
                 }
@@ -286,10 +286,10 @@
      * dozing.
      */
     @VisibleForTesting
-    internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+    internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
         return scope.launch {
-            keyguardTransitionInteractor.goneToAodTransition.filter {
-                it.transitionState == TransitionState.STARTED
+            keyguardTransitionInteractor.anyStateToAodTransition.filter {
+                it.transitionState == TransitionState.FINISHED
             }.collect {
                 dozeAmount = 1f
                 clock?.animations?.doze(dozeAmount)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c756a17..2bb3a5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -106,8 +106,9 @@
     static final int USER_TYPE_WORK_PROFILE = 2;
     static final int USER_TYPE_SECONDARY_USER = 3;
 
-    @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+    @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
     public @interface Mode {}
+    static final int MODE_UNINITIALIZED = -1;
     static final int MODE_DEFAULT = 0;
     static final int MODE_ONE_HANDED = 1;
     static final int MODE_USER_SWITCHER = 2;
@@ -154,7 +155,11 @@
     private boolean mDisappearAnimRunning;
     private SwipeListener mSwipeListener;
     private ViewMode mViewMode = new DefaultViewMode();
-    private @Mode int mCurrentMode = MODE_DEFAULT;
+    /*
+     * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+     * yet been called on it. This will happen when the ViewController is initialized.
+     */
+    private @Mode int mCurrentMode = MODE_UNINITIALIZED;
     private int mWidth = -1;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -347,6 +352,8 @@
 
     private String modeToString(@Mode int mode) {
         switch (mode) {
+            case MODE_UNINITIALIZED:
+                return "Uninitialized";
             case MODE_DEFAULT:
                 return "Default";
             case MODE_ONE_HANDED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 79a01b9..7a49926 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -58,7 +58,8 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
+import com.android.systemui.biometrics.SideFpsUiRequestSource;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
@@ -100,7 +101,7 @@
     private final GlobalSettings mGlobalSettings;
     private final FeatureFlags mFeatureFlags;
     private final SessionTracker mSessionTracker;
-    private final Optional<SidefpsController> mSidefpsController;
+    private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -290,7 +291,7 @@
             FeatureFlags featureFlags,
             GlobalSettings globalSettings,
             SessionTracker sessionTracker,
-            Optional<SidefpsController> sidefpsController,
+            Optional<SideFpsController> sideFpsController,
             FalsingA11yDelegate falsingA11yDelegate) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
@@ -311,13 +312,14 @@
         mFeatureFlags = featureFlags;
         mGlobalSettings = globalSettings;
         mSessionTracker = sessionTracker;
-        mSidefpsController = sidefpsController;
+        mSideFpsController = sideFpsController;
         mFalsingA11yDelegate = falsingA11yDelegate;
     }
 
     @Override
     public void onInit() {
         mSecurityViewFlipperController.init();
+        configureMode();
     }
 
     @Override
@@ -350,7 +352,7 @@
     }
 
     private void updateSideFpsVisibility() {
-        if (!mSidefpsController.isPresent()) {
+        if (!mSideFpsController.isPresent()) {
             return;
         }
         final boolean sfpsEnabled = getResources().getBoolean(
@@ -368,9 +370,9 @@
                     + "needsStrongAuth=" + needsStrongAuth);
         }
         if (toShow) {
-            mSidefpsController.get().show();
+            mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
         } else {
-            mSidefpsController.get().hide();
+            mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
         }
     }
 
@@ -744,7 +746,7 @@
         private final FeatureFlags mFeatureFlags;
         private final UserSwitcherController mUserSwitcherController;
         private final SessionTracker mSessionTracker;
-        private final Optional<SidefpsController> mSidefpsController;
+        private final Optional<SideFpsController> mSidefpsController;
         private final FalsingA11yDelegate mFalsingA11yDelegate;
 
         @Inject
@@ -765,7 +767,7 @@
                 FeatureFlags featureFlags,
                 GlobalSettings globalSettings,
                 SessionTracker sessionTracker,
-                Optional<SidefpsController> sidefpsController,
+                Optional<SideFpsController> sidefpsController,
                 FalsingA11yDelegate falsingA11yDelegate) {
             mView = view;
             mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9d2dcf0..055f376 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -298,8 +298,8 @@
     private boolean mCredentialAttempted;
     private boolean mKeyguardGoingAway;
     private boolean mGoingToSleep;
-    private boolean mBouncerFullyShown;
-    private boolean mBouncerIsOrWillBeShowing;
+    private boolean mPrimaryBouncerFullyShown;
+    private boolean mPrimaryBouncerIsOrWillBeShowing;
     private boolean mUdfpsBouncerShowing;
     private boolean mAuthInterruptActive;
     private boolean mNeedsSlowUnlockTransition;
@@ -782,7 +782,7 @@
         mFingerprintCancelSignal = null;
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_FP_AUTHENTICATED);
-        mLogger.d("onFingerprintAuthenticated");
+        mLogger.logFingerprintSuccess(userId, isStrongBiometric);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1648,8 +1648,9 @@
                 public void onAuthenticationFailed() {
                         String reason =
                                 mKeyguardBypassController.canBypass() ? "bypass"
-                                        : mUdfpsBouncerShowing ? "udfpsBouncer" :
-                                                mBouncerFullyShown ? "bouncer" : "udfpsFpDown";
+                                        : mUdfpsBouncerShowing ? "udfpsBouncer"
+                                                : mPrimaryBouncerFullyShown ? "bouncer"
+                                                        : "udfpsFpDown";
                         requestActiveUnlock(
                                 ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
                                 "faceFailure-" + reason);
@@ -2041,7 +2042,7 @@
                         handleKeyguardReset();
                         break;
                     case MSG_KEYGUARD_BOUNCER_CHANGED:
-                        handleKeyguardBouncerChanged(msg.arg1, msg.arg2);
+                        handlePrimaryBouncerChanged(msg.arg1, msg.arg2);
                         break;
                     case MSG_REPORT_EMERGENCY_CALL_ACTION:
                         handleReportEmergencyCallAction();
@@ -2511,7 +2512,7 @@
                 requestOrigin,
                 extraReason, canFaceBypass
                         || mUdfpsBouncerShowing
-                        || mBouncerFullyShown
+                        || mPrimaryBouncerFullyShown
                         || mAuthController.isUdfpsFingerDown());
     }
 
@@ -2532,7 +2533,7 @@
     private boolean shouldTriggerActiveUnlock() {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
-        final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing
+        final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mUdfpsBouncerShowing
                 || (isKeyguardVisible() && !mGoingToSleep
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
@@ -2611,7 +2612,7 @@
         final boolean shouldListenKeyguardState =
                 isKeyguardVisible()
                         || !mDeviceInteractive
-                        || (mBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
+                        || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
                         || mGoingToSleep
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
@@ -2630,8 +2631,8 @@
                         && mIsPrimaryUser
                         && biometricEnabledForUser;
 
-        final boolean shouldListenBouncerState =
-                !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
+        final boolean shouldListenBouncerState = !(mFingerprintLockedOut
+                && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted);
 
         final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
 
@@ -2656,7 +2657,7 @@
                     user,
                     shouldListen,
                     biometricEnabledForUser,
-                    mBouncerIsOrWillBeShowing,
+                        mPrimaryBouncerIsOrWillBeShowing,
                     userCanSkipBouncer,
                     mCredentialAttempted,
                     mDeviceInteractive,
@@ -2701,7 +2702,7 @@
                         || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
 
         // TODO: always disallow when fp is already locked out?
-        final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+        final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
 
         final boolean canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
@@ -2713,7 +2714,7 @@
         // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
         // Lock-down mode shouldn't scan, since it is more explicit.
         boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
-                && !mBouncerFullyShown);
+                && !mPrimaryBouncerFullyShown);
 
         // If the device supports face detection (without authentication) and bypass is enabled,
         // allow face scanning to happen if the device is in lockdown mode.
@@ -2730,12 +2731,11 @@
         final boolean faceDisabledForUser = isFaceDisabled(user);
         final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
         final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
-        final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
 
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
-                (mBouncerFullyShown
+                (mPrimaryBouncerFullyShown
                         || mAuthInterruptActive
                         || mOccludingAppRequestingFace
                         || awakeKeyguard
@@ -2748,7 +2748,10 @@
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
                 && !faceAuthenticated
                 && !mGoingToSleep
-                && !fpOrFaceIsLockedOut;
+                // We only care about fp locked out state and not face because we still trigger
+                // face auth even when face is locked out to show the user a message that face
+                // unlock was supposed to run but didn't
+                && !fpLockedOut;
 
         // Aggregate relevant fields for debug logging.
         maybeLogListenerModelData(
@@ -2759,11 +2762,11 @@
                     mAuthInterruptActive,
                     becauseCannotSkipBouncer,
                     biometricEnabledForUser,
-                    mBouncerFullyShown,
+                        mPrimaryBouncerFullyShown,
                     faceAuthenticated,
                     faceDisabledForUser,
                     isFaceLockedOut(),
-                    fpLockedout,
+                    fpLockedOut,
                     mGoingToSleep,
                     awakeKeyguard,
                     mKeyguardGoingAway,
@@ -3289,17 +3292,19 @@
     /**
      * Handle {@link #MSG_KEYGUARD_BOUNCER_CHANGED}
      *
-     * @see #sendKeyguardBouncerChanged(boolean, boolean)
+     * @see #sendPrimaryBouncerChanged(boolean, boolean)
      */
-    private void handleKeyguardBouncerChanged(int bouncerIsOrWillBeShowing, int bouncerFullyShown) {
+    private void handlePrimaryBouncerChanged(int primaryBouncerIsOrWillBeShowing,
+            int primaryBouncerFullyShown) {
         Assert.isMainThread();
-        final boolean wasBouncerIsOrWillBeShowing = mBouncerIsOrWillBeShowing;
-        final boolean wasBouncerFullyShown = mBouncerFullyShown;
-        mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
-        mBouncerFullyShown = bouncerFullyShown == 1;
-        mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
+        final boolean wasPrimaryBouncerIsOrWillBeShowing = mPrimaryBouncerIsOrWillBeShowing;
+        final boolean wasPrimaryBouncerFullyShown = mPrimaryBouncerFullyShown;
+        mPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing == 1;
+        mPrimaryBouncerFullyShown = primaryBouncerFullyShown == 1;
+        mLogger.logPrimaryKeyguardBouncerChanged(mPrimaryBouncerIsOrWillBeShowing,
+                mPrimaryBouncerFullyShown);
 
-        if (mBouncerFullyShown) {
+        if (mPrimaryBouncerFullyShown) {
             // If the bouncer is shown, always clear this flag. This can happen in the following
             // situations: 1) Default camera with SHOW_WHEN_LOCKED is not chosen yet. 2) Secure
             // camera requests dismiss keyguard (tapping on photos for example). When these happen,
@@ -3309,18 +3314,18 @@
             mCredentialAttempted = false;
         }
 
-        if (wasBouncerIsOrWillBeShowing != mBouncerIsOrWillBeShowing) {
+        if (wasPrimaryBouncerIsOrWillBeShowing != mPrimaryBouncerIsOrWillBeShowing) {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
-                    cb.onKeyguardBouncerStateChanged(mBouncerIsOrWillBeShowing);
+                    cb.onKeyguardBouncerStateChanged(mPrimaryBouncerIsOrWillBeShowing);
                 }
             }
             updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         }
 
-        if (wasBouncerFullyShown != mBouncerFullyShown) {
-            if (mBouncerFullyShown) {
+        if (wasPrimaryBouncerFullyShown != mPrimaryBouncerFullyShown) {
+            if (mPrimaryBouncerFullyShown) {
                 requestActiveUnlock(
                         ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
                         "bouncerFullyShown");
@@ -3328,7 +3333,7 @@
             for (int i = 0; i < mCallbacks.size(); i++) {
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                 if (cb != null) {
-                    cb.onKeyguardBouncerFullyShowingChanged(mBouncerFullyShown);
+                    cb.onKeyguardBouncerFullyShowingChanged(mPrimaryBouncerFullyShown);
                 }
             }
             updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -3479,14 +3484,15 @@
     }
 
     /**
-     * @see #handleKeyguardBouncerChanged(int, int)
+     * @see #handlePrimaryBouncerChanged(int, int)
      */
-    public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
-            boolean bouncerFullyShown) {
-        mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
+    public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing,
+            boolean primaryBouncerFullyShown) {
+        mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+                primaryBouncerFullyShown);
         Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
-        message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
-        message.arg2 = bouncerFullyShown ? 1 : 0;
+        message.arg1 = primaryBouncerIsOrWillBeShowing ? 1 : 0;
+        message.arg2 = primaryBouncerFullyShown ? 1 : 0;
         message.sendToTarget();
     }
 
@@ -3846,7 +3852,8 @@
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
-                pw.println("        mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
+                pw.println("        mPrimaryBouncerIsOrWillBeShowing="
+                        + mPrimaryBouncerIsOrWillBeShowing);
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
                 pw.println("        mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
             } else if (isSfpsSupported()) {
@@ -3878,7 +3885,7 @@
             pw.println("    mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             pw.println("    mSecureCameraLaunched=" + mSecureCameraLaunched);
-            pw.println("    mBouncerFullyShown=" + mBouncerFullyShown);
+            pw.println("    mPrimaryBouncerFullyShown=" + mPrimaryBouncerFullyShown);
             pw.println("    mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition);
         }
         mListenModels.print(pw);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 90f0446..6c3c246 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -50,16 +50,11 @@
 
     /**
      * Resets the state of Keyguard View.
-     * @param hideBouncerWhenShowing
+     * @param hideBouncerWhenShowing when true, hides the primary and alternate bouncers if showing.
      */
     void reset(boolean hideBouncerWhenShowing);
 
     /**
-     * Stop showing any alternate auth methods.
-     */
-    void resetAlternateAuth(boolean forceUpdateScrim);
-
-    /**
      * Called when the device started going to sleep.
      */
     default void onStartedGoingToSleep() {};
@@ -156,20 +151,24 @@
     void notifyKeyguardAuthenticated(boolean strongAuth);
 
     /**
-     * Shows the Bouncer.
-     *
+     * Shows the primary bouncer.
      */
-    void showBouncer(boolean scrimmed);
+    void showPrimaryBouncer(boolean scrimmed);
 
     /**
-     * Returns {@code true} when the bouncer is currently showing
+     * When the primary bouncer is fully visible or is showing but animation didn't finish yet.
+     */
+    boolean primaryBouncerIsOrWillBeShowing();
+
+    /**
+     * Returns {@code true} when the primary bouncer or alternate bouncer is currently showing
      */
     boolean isBouncerShowing();
 
     /**
-     * When bouncer is fully visible or it is showing but animation didn't finish yet.
+     * Stop showing the alternate bouncer, if showing.
      */
-    boolean bouncerIsOrWillBeShowing();
+    void hideAlternateBouncer(boolean forceUpdateScrim);
 
     // TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
     //  only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 0a82968..34a5ef7 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -158,6 +158,10 @@
         return mLockIconCenter.y - mRadius;
     }
 
+    float getLocationBottom() {
+        return mLockIconCenter.y + mRadius;
+    }
+
     /**
      * Updates the icon its default state where no visual is shown.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8fbbd38..cf246b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -282,6 +282,10 @@
         return mView.getLocationTop();
     }
 
+    public float getBottom() {
+        return mView.getLocationBottom();
+    }
+
     private void updateVisibility() {
         if (mCancelDelayedUpdateVisibilityRunnable != null) {
             mCancelDelayedUpdateVisibilityRunnable.run();
@@ -697,7 +701,7 @@
                 "lock-screen-lock-icon-longpress",
                 TOUCH_VIBRATION_ATTRIBUTES);
 
-        mKeyguardViewController.showBouncer(/* scrim */ true);
+        mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index 49e9783..ef067b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -16,7 +16,7 @@
 
 package com.android.keyguard.dagger;
 
-import static com.android.systemui.biometrics.SidefpsControllerKt.hasSideFpsSensor;
+import static com.android.systemui.biometrics.SideFpsControllerKt.hasSideFpsSensor;
 
 import android.annotation.Nullable;
 import android.hardware.fingerprint.FingerprintManager;
@@ -27,7 +27,7 @@
 import com.android.keyguard.KeyguardSecurityContainer;
 import com.android.keyguard.KeyguardSecurityViewFlipper;
 import com.android.systemui.R;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
@@ -70,12 +70,12 @@
         return containerView.findViewById(R.id.view_flipper);
     }
 
-    /** Provides {@link SidefpsController} if the device has the side fingerprint sensor. */
+    /** Provides {@link SideFpsController} if the device has the side fingerprint sensor. */
     @Provides
     @KeyguardBouncerScope
-    static Optional<SidefpsController> providesOptionalSidefpsController(
+    static Optional<SideFpsController> providesOptionalSidefpsController(
             @Nullable FingerprintManager fingerprintManager,
-            Provider<SidefpsController> sidefpsControllerProvider) {
+            Provider<SideFpsController> sidefpsControllerProvider) {
         if (!hasSideFpsSensor(fingerprintManager)) {
             return Optional.empty();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 3308f55..81b8dfe 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -153,19 +153,29 @@
                 { "fingerprintRunningState: $int1" })
     }
 
+    fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
+        logBuffer.log(TAG, DEBUG, {
+            int1 = userId
+            bool1 = isStrongBiometric
+        }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
+    }
+
     fun logInvalidSubId(subId: Int) {
         logBuffer.log(TAG, INFO,
                 { int1 = subId },
                 { "Previously active sub id $int1 is now invalid, will remove" })
     }
 
-    fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+    fun logPrimaryKeyguardBouncerChanged(
+            primaryBouncerIsOrWillBeShowing: Boolean,
+            primaryBouncerFullyShown: Boolean
+    ) {
         logBuffer.log(TAG, DEBUG, {
-            bool1 = bouncerIsOrWillBeShowing
-            bool2 = bouncerFullyShown
+            bool1 = primaryBouncerIsOrWillBeShowing
+            bool2 = primaryBouncerFullyShown
         }, {
-            "handleKeyguardBouncerChanged " +
-                    "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+            "handlePrimaryBouncerChanged " +
+                    "primaryBouncerIsOrWillBeShowing=$bool1 primaryBouncerFullyShown=$bool2"
         })
     }
 
@@ -222,16 +232,16 @@
                 { "Retrying fingerprint attempt: $int1" })
     }
 
-    fun logSendKeyguardBouncerChanged(
-        bouncerIsOrWillBeShowing: Boolean,
-        bouncerFullyShown: Boolean,
+    fun logSendPrimaryBouncerChanged(
+        primaryBouncerIsOrWillBeShowing: Boolean,
+        primaryBouncerFullyShown: Boolean,
     ) {
         logBuffer.log(TAG, DEBUG, {
-            bool1 = bouncerIsOrWillBeShowing
-            bool2 = bouncerFullyShown
+            bool1 = primaryBouncerIsOrWillBeShowing
+            bool2 = primaryBouncerFullyShown
         }, {
-            "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
-                    "bouncerFullyShown=$bool2"
+            "sendPrimaryBouncerChanged primaryBouncerIsOrWillBeShowing=$bool1 " +
+                    "primaryBouncerFullyShown=$bool2"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 3015710..eee705d 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -26,8 +26,6 @@
 
 import kotlin.math.roundToInt
 
-const val TAG = "CameraAvailabilityListener"
-
 /**
  * Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
  * protection around a display cutout based on config_frontBuiltInDisplayCutoutProtection and
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 47ee71e..0e7deeb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -269,7 +269,11 @@
     }
 
     private static void notifyBootCompleted(CoreStartable coreStartable) {
-        Trace.beginSection(coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP,
+                    coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+        }
         coreStartable.onBootCompleted();
         Trace.endSection();
     }
@@ -291,14 +295,18 @@
     private static CoreStartable startAdditionalStartable(String clsName) {
         CoreStartable startable;
         if (DEBUG) Log.d(TAG, "loading: " + clsName);
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, clsName + ".newInstance()");
+        }
         try {
-            Trace.beginSection(clsName + ".newInstance()");
             startable = (CoreStartable) Class.forName(clsName).newInstance();
-            Trace.endSection();
         } catch (ClassNotFoundException
                 | IllegalAccessException
                 | InstantiationException ex) {
             throw new RuntimeException(ex);
+        } finally {
+            Trace.endSection();
         }
 
         return startStartable(startable);
@@ -306,7 +314,10 @@
 
     private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
         if (DEBUG) Log.d(TAG, "loading: " + clsName);
-        Trace.beginSection("Provider<" + clsName + ">.get()");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, "Provider<" + clsName + ">.get()");
+        }
         CoreStartable startable = provider.get();
         Trace.endSection();
         return startStartable(startable);
@@ -314,7 +325,10 @@
 
     private static CoreStartable startStartable(CoreStartable startable) {
         if (DEBUG) Log.d(TAG, "running: " + startable);
-        Trace.beginSection(startable.getClass().getSimpleName() + ".start()");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, startable.getClass().getSimpleName() + ".start()");
+        }
         startable.start();
         Trace.endSection();
 
@@ -355,15 +369,22 @@
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
             ConfigurationController configController = mSysUIComponent.getConfigurationController();
-            Trace.beginSection(
-                    configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+            if (Trace.isEnabled()) {
+                Trace.traceBegin(
+                        Trace.TRACE_TAG_APP,
+                        configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+            }
             configController.onConfigurationChanged(newConfig);
             Trace.endSection();
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
-                    Trace.beginSection(
-                            mServices[i].getClass().getSimpleName() + ".onConfigurationChanged()");
+                    if (Trace.isEnabled()) {
+                        Trace.traceBegin(
+                                Trace.TRACE_TAG_APP,
+                                mServices[i].getClass().getSimpleName()
+                                        + ".onConfigurationChanged()");
+                    }
                     mServices[i].onConfigurationChanged(newConfig);
                     Trace.endSection();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index a21f45f..632fcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -97,7 +97,6 @@
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
                     .setBackAnimation(mWMComponent.getBackAnimation())
-                    .setFloatingTasks(mWMComponent.getFloatingTasks())
                     .setDesktopMode(mWMComponent.getDesktopMode());
 
             // Only initialize when not starting from tests since this currently initializes some
@@ -118,7 +117,6 @@
                     .setStartingSurface(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
                     .setBackAnimation(Optional.ofNullable(null))
-                    .setFloatingTasks(Optional.ofNullable(null))
                     .setDesktopMode(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 8920c92..8aa3040 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -17,7 +17,7 @@
 package com.android.systemui
 
 import android.content.Context
-import com.android.systemui.dagger.DaggerGlobalRootComponent
+import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
 import com.android.systemui.dagger.GlobalRootComponent
 
 /**
@@ -25,6 +25,6 @@
  */
 class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
     override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
-        return DaggerGlobalRootComponent.builder()
+        return DaggerReferenceGlobalRootComponent.builder()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index b40b356..b2a2a67 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.RawRes
 import android.content.Context
+import android.content.res.Configuration
 import android.hardware.fingerprint.FingerprintManager
 import android.view.DisplayInfo
 import android.view.Surface
@@ -33,15 +34,19 @@
 import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
 import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
 import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.unfold.updates.FoldProvider
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
         context: Context,
         iconView: LottieAnimationView,
         protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
+) : AuthIconController(context, iconView), FoldProvider.FoldCallback {
 
+    private var isDeviceFolded: Boolean = false
     private val isSideFps: Boolean
+    private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
     var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
             if (field == value) {
@@ -74,6 +79,8 @@
         if (isSideFps && displayInfo.rotation == Surface.ROTATION_180) {
             iconView.rotation = 180f
         }
+        screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
+        screenSizeFoldProvider.onConfigurationChange(context.resources.configuration)
     }
 
     private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
@@ -124,6 +131,10 @@
         LottieColorUtils.applyDynamicColors(context, iconView)
     }
 
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        screenSizeFoldProvider.onConfigurationChange(newConfig)
+    }
+
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
         if (isSideFps) {
             updateIconSideFps(lastState, newState)
@@ -191,11 +202,21 @@
 
     @RawRes
     private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
-        Surface.ROTATION_0 -> R.raw.biometricprompt_landscape_base
-        Surface.ROTATION_90 -> R.raw.biometricprompt_portrait_base_topleft
-        Surface.ROTATION_180 -> R.raw.biometricprompt_landscape_base
-        Surface.ROTATION_270 -> R.raw.biometricprompt_portrait_base_bottomright
-        else -> R.raw.biometricprompt_landscape_base
+        Surface.ROTATION_90 -> if (isDeviceFolded) {
+            R.raw.biometricprompt_folded_base_topleft
+        } else {
+            R.raw.biometricprompt_portrait_base_topleft
+        }
+        Surface.ROTATION_270 -> if (isDeviceFolded) {
+            R.raw.biometricprompt_folded_base_bottomright
+        } else {
+            R.raw.biometricprompt_portrait_base_bottomright
+        }
+        else -> if (isDeviceFolded) {
+            R.raw.biometricprompt_folded_base_default
+        } else {
+            R.raw.biometricprompt_landscape_base
+        }
     }
 
     @RawRes
@@ -273,4 +294,8 @@
         }
         else -> null
     }
+
+    override fun onFoldUpdated(isFolded: Boolean) {
+        isDeviceFolded = isFolded
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index 15f487b..b3b6fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.DrawableRes
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.drawable.Animatable2
 import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
@@ -91,4 +92,6 @@
 
     /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
     open fun handleAnimationEnd(drawable: Drawable) {}
+
+    open fun onConfigurationChanged(newConfig: Configuration) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 0ac71c4..e12c170 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.PromptInfo;
@@ -654,6 +655,12 @@
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mIconController.onConfigurationChanged(newConfig);
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0cf3cfe..a7519cf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -122,7 +122,7 @@
     @Nullable private final FingerprintManager mFingerprintManager;
     @Nullable private final FaceManager mFaceManager;
     private final Provider<UdfpsController> mUdfpsControllerFactory;
-    private final Provider<SidefpsController> mSidefpsControllerFactory;
+    private final Provider<SideFpsController> mSidefpsControllerFactory;
 
     // TODO: these should be migrated out once ready
     @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
@@ -148,7 +148,7 @@
     @NonNull private final DisplayManager mDisplayManager;
     @Nullable private UdfpsController mUdfpsController;
     @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
-    @Nullable private SidefpsController mSidefpsController;
+    @Nullable private SideFpsController mSideFpsController;
     @Nullable private IBiometricContextListener mBiometricContextListener;
     @Nullable private UdfpsLogger mUdfpsLogger;
     @VisibleForTesting IBiometricSysuiReceiver mReceiver;
@@ -306,7 +306,7 @@
 
         mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
         if (mSidefpsProps != null) {
-            mSidefpsController = mSidefpsControllerFactory.get();
+            mSideFpsController = mSidefpsControllerFactory.get();
         }
 
         mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
@@ -702,7 +702,7 @@
             @Nullable FingerprintManager fingerprintManager,
             @Nullable FaceManager faceManager,
             Provider<UdfpsController> udfpsControllerFactory,
-            Provider<SidefpsController> sidefpsControllerFactory,
+            Provider<SideFpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
             @NonNull UserManager userManager,
@@ -810,17 +810,26 @@
     private void updateUdfpsLocation() {
         if (mUdfpsController != null) {
             final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+
             final Rect previousUdfpsBounds = mUdfpsBounds;
             mUdfpsBounds = udfpsProp.getLocation().getRect();
             mUdfpsBounds.scale(mScaleFactor);
-            mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
-                    new UdfpsOverlayParams(mUdfpsBounds, new Rect(
-                            0, mCachedDisplayInfo.getNaturalHeight() / 2,
-                            mCachedDisplayInfo.getNaturalWidth(),
-                            mCachedDisplayInfo.getNaturalHeight()),
-                            mCachedDisplayInfo.getNaturalWidth(),
-                            mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,
-                            mCachedDisplayInfo.rotation));
+
+            final Rect overlayBounds = new Rect(
+                    0, /* left */
+                    mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
+                    mCachedDisplayInfo.getNaturalWidth(), /* right */
+                    mCachedDisplayInfo.getNaturalHeight() /* botom */);
+
+            final UdfpsOverlayParams overlayParams = new UdfpsOverlayParams(
+                    mUdfpsBounds,
+                    overlayBounds,
+                    mCachedDisplayInfo.getNaturalWidth(),
+                    mCachedDisplayInfo.getNaturalHeight(),
+                    mScaleFactor,
+                    mCachedDisplayInfo.rotation);
+
+            mUdfpsController.updateOverlayParams(udfpsProp, overlayParams);
             if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
                 for (Callback cb : mCallbacks) {
                     cb.onUdfpsLocationChanged();
@@ -1111,7 +1120,7 @@
      * Whether the passed userId has enrolled SFPS.
      */
     public boolean isSfpsEnrolled(int userId) {
-        if (mSidefpsController == null) {
+        if (mSideFpsController == null) {
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
new file mode 100644
index 0000000..1c3dd45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -0,0 +1,397 @@
+/*
+ * 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.biometrics
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.app.ActivityTaskManager
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.util.Log
+import android.util.RotationUtils
+import android.view.Display
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.accessibility.AccessibilityEvent
+import androidx.annotation.RawRes
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "SideFpsController"
+
+/**
+ * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
+ */
+@SysUISingleton
+class SideFpsController
+@Inject
+constructor(
+    private val context: Context,
+    private val layoutInflater: LayoutInflater,
+    fingerprintManager: FingerprintManager?,
+    private val windowManager: WindowManager,
+    private val activityTaskManager: ActivityTaskManager,
+    overviewProxyService: OverviewProxyService,
+    displayManager: DisplayManager,
+    @Main private val mainExecutor: DelayableExecutor,
+    @Main private val handler: Handler,
+    dumpManager: DumpManager
+) : Dumpable {
+    val requests: HashSet<SideFpsUiRequestSource> = HashSet()
+
+    @VisibleForTesting
+    val sensorProps: FingerprintSensorPropertiesInternal =
+        fingerprintManager?.sideFpsSensorProperties
+            ?: throw IllegalStateException("no side fingerprint sensor")
+
+    @VisibleForTesting
+    val orientationListener =
+        BiometricDisplayListener(
+            context,
+            displayManager,
+            handler,
+            BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
+        ) { onOrientationChanged() }
+
+    @VisibleForTesting
+    val overviewProxyListener =
+        object : OverviewProxyService.OverviewProxyListener {
+            override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
+                overlayView?.let { view ->
+                    handler.postDelayed({ updateOverlayVisibility(view) }, 500)
+                }
+            }
+        }
+
+    private val animationDuration =
+        context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
+
+    private var overlayHideAnimator: ViewPropertyAnimator? = null
+
+    private var overlayView: View? = null
+        set(value) {
+            field?.let { oldView ->
+                windowManager.removeView(oldView)
+                orientationListener.disable()
+            }
+            overlayHideAnimator?.cancel()
+            overlayHideAnimator = null
+
+            field = value
+            field?.let { newView ->
+                windowManager.addView(newView, overlayViewParams)
+                updateOverlayVisibility(newView)
+                orientationListener.enable()
+            }
+        }
+    @VisibleForTesting
+    internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+
+    private val overlayViewParams =
+        WindowManager.LayoutParams(
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
+                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+                PixelFormat.TRANSLUCENT
+            )
+            .apply {
+                title = TAG
+                fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+                gravity = Gravity.TOP or Gravity.LEFT
+                layoutInDisplayCutoutMode =
+                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
+            }
+
+    init {
+        fingerprintManager?.setSidefpsController(
+            object : ISidefpsController.Stub() {
+                override fun show(
+                    sensorId: Int,
+                    @BiometricOverlayConstants.ShowReason reason: Int
+                ) =
+                    if (reason.isReasonToAutoShow(activityTaskManager)) {
+                        show(SideFpsUiRequestSource.AUTO_SHOW)
+                    } else {
+                        hide(SideFpsUiRequestSource.AUTO_SHOW)
+                    }
+
+                override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
+            }
+        )
+        overviewProxyService.addCallback(overviewProxyListener)
+        dumpManager.registerDumpable(this)
+    }
+
+    /** Shows the side fps overlay if not already shown. */
+    fun show(request: SideFpsUiRequestSource) {
+        requests.add(request)
+        mainExecutor.execute {
+            if (overlayView == null) {
+                createOverlayForDisplay()
+            } else {
+                Log.v(TAG, "overlay already shown")
+            }
+        }
+    }
+
+    /** Hides the fps overlay if shown. */
+    fun hide(request: SideFpsUiRequestSource) {
+        requests.remove(request)
+        mainExecutor.execute {
+            if (requests.isEmpty()) {
+                overlayView = null
+            }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("requests:")
+        for (requestSource in requests) {
+            pw.println("     $requestSource.name")
+        }
+    }
+
+    private fun onOrientationChanged() {
+        if (overlayView != null) {
+            createOverlayForDisplay()
+        }
+    }
+
+    private fun createOverlayForDisplay() {
+        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+        overlayView = view
+        val display = context.display!!
+        val offsets =
+            sensorProps.getLocation(display.uniqueId).let { location ->
+                if (location == null) {
+                    Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+                }
+                location ?: sensorProps.location
+            }
+        overlayOffsets = offsets
+
+        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
+        lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
+        lottie.addLottieOnCompositionLoadedListener {
+            // Check that view is not stale, and that overlayView has not been hidden/removed
+            if (overlayView != null && overlayView == view) {
+                updateOverlayParams(display, it.bounds)
+            }
+        }
+        lottie.addOverlayDynamicColor(context)
+
+        /**
+         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+         * in focus
+         */
+        view.setAccessibilityDelegate(
+            object : AccessibilityDelegate() {
+                override fun dispatchPopulateAccessibilityEvent(
+                    host: View,
+                    event: AccessibilityEvent
+                ): Boolean {
+                    return if (
+                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                    ) {
+                        true
+                    } else {
+                        super.dispatchPopulateAccessibilityEvent(host, event)
+                    }
+                }
+            }
+        )
+    }
+
+    @VisibleForTesting
+    internal fun updateOverlayParams(display: Display, bounds: Rect) {
+        val isNaturalOrientation = display.isNaturalOrientation()
+        val size = windowManager.maximumWindowMetrics.bounds
+        val displayWidth = if (isNaturalOrientation) size.width() else size.height()
+        val displayHeight = if (isNaturalOrientation) size.height() else size.width()
+        val boundsWidth = if (isNaturalOrientation) bounds.width() else bounds.height()
+        val boundsHeight = if (isNaturalOrientation) bounds.height() else bounds.width()
+        val sensorBounds =
+            if (overlayOffsets.isYAligned()) {
+                Rect(
+                    displayWidth - boundsWidth,
+                    overlayOffsets.sensorLocationY,
+                    displayWidth,
+                    overlayOffsets.sensorLocationY + boundsHeight
+                )
+            } else {
+                Rect(
+                    overlayOffsets.sensorLocationX,
+                    0,
+                    overlayOffsets.sensorLocationX + boundsWidth,
+                    boundsHeight
+                )
+            }
+
+        RotationUtils.rotateBounds(
+            sensorBounds,
+            Rect(0, 0, displayWidth, displayHeight),
+            display.rotation
+        )
+
+        overlayViewParams.x = sensorBounds.left
+        overlayViewParams.y = sensorBounds.top
+        windowManager.updateViewLayout(overlayView, overlayViewParams)
+    }
+
+    private fun updateOverlayVisibility(view: View) {
+        if (view != overlayView) {
+            return
+        }
+        // hide after a few seconds if the sensor is oriented down and there are
+        // large overlapping system bars
+        val rotation = context.display?.rotation
+        if (
+            windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
+                ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
+                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))
+        ) {
+            overlayHideAnimator =
+                view
+                    .animate()
+                    .alpha(0f)
+                    .setStartDelay(3_000)
+                    .setDuration(animationDuration)
+                    .setListener(
+                        object : AnimatorListenerAdapter() {
+                            override fun onAnimationEnd(animation: Animator) {
+                                view.visibility = View.GONE
+                                overlayHideAnimator = null
+                            }
+                        }
+                    )
+        } else {
+            overlayHideAnimator?.cancel()
+            overlayHideAnimator = null
+            view.alpha = 1f
+            view.visibility = View.VISIBLE
+        }
+    }
+}
+
+private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
+    get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
+
+/** Returns [True] when the device has a side fingerprint sensor. */
+fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
+
+@BiometricOverlayConstants.ShowReason
+private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Boolean =
+    when (this) {
+        REASON_AUTH_KEYGUARD -> false
+        REASON_AUTH_SETTINGS ->
+            when (activityTaskManager.topClass()) {
+                // TODO(b/186176653): exclude fingerprint overlays from this list view
+                "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
+                else -> true
+            }
+        else -> true
+    }
+
+private fun ActivityTaskManager.topClass(): String =
+    getTasks(1).firstOrNull()?.topActivity?.className ?: ""
+
+@RawRes
+private fun Display.asSideFpsAnimation(yAligned: Boolean): Int =
+    when (rotation) {
+        Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+        Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+        else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+    }
+
+private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float =
+    when (rotation) {
+        Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+        Surface.ROTATION_180 -> 180f
+        Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+        else -> 0f
+    }
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
+
+private fun Display.isNaturalOrientation(): Boolean =
+    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun WindowInsets.hasBigNavigationBar(): Boolean =
+    getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
+
+private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
+    fun update() {
+        val c = context.getColor(R.color.biometric_dialog_accent)
+        for (key in listOf(".blue600", ".blue400")) {
+            addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
+            }
+        }
+    }
+
+    if (composition != null) {
+        update()
+    } else {
+        addLottieOnCompositionLoadedListener { update() }
+    }
+}
+
+/**
+ * The source of a request to show the side fps visual indicator. This is distinct from
+ * [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is
+ * requested.
+ */
+enum class SideFpsUiRequestSource {
+    /** see [isReasonToAutoShow] */
+    AUTO_SHOW,
+    /** Pin, pattern or password bouncer */
+    PRIMARY_BOUNCER,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
deleted file mode 100644
index d03106b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.app.ActivityTaskManager
-import android.content.Context
-import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.display.DisplayManager
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.ISidefpsController
-import android.os.Handler
-import android.util.Log
-import android.util.RotationUtils
-import android.view.Display
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.Surface
-import android.view.View
-import android.view.View.AccessibilityDelegate
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
-import javax.inject.Inject
-
-private const val TAG = "SidefpsController"
-
-/**
- * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
- */
-@SysUISingleton
-class SidefpsController @Inject constructor(
-    private val context: Context,
-    private val layoutInflater: LayoutInflater,
-    fingerprintManager: FingerprintManager?,
-    private val windowManager: WindowManager,
-    private val activityTaskManager: ActivityTaskManager,
-    overviewProxyService: OverviewProxyService,
-    displayManager: DisplayManager,
-    @Main private val mainExecutor: DelayableExecutor,
-    @Main private val handler: Handler
-) {
-    @VisibleForTesting
-    val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
-        ?.sideFpsSensorProperties
-        ?: throw IllegalStateException("no side fingerprint sensor")
-
-    @VisibleForTesting
-    val orientationListener = BiometricDisplayListener(
-        context,
-        displayManager,
-        handler,
-        BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
-    ) { onOrientationChanged() }
-
-    @VisibleForTesting
-    val overviewProxyListener = object : OverviewProxyService.OverviewProxyListener {
-        override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
-            overlayView?.let { view ->
-                handler.postDelayed({ updateOverlayVisibility(view) }, 500)
-            }
-        }
-    }
-
-    private val animationDuration =
-        context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
-
-    private var overlayHideAnimator: ViewPropertyAnimator? = null
-
-    private var overlayView: View? = null
-        set(value) {
-            field?.let { oldView ->
-                windowManager.removeView(oldView)
-                orientationListener.disable()
-            }
-            overlayHideAnimator?.cancel()
-            overlayHideAnimator = null
-
-            field = value
-            field?.let { newView ->
-                windowManager.addView(newView, overlayViewParams)
-                updateOverlayVisibility(newView)
-                orientationListener.enable()
-            }
-        }
-    @VisibleForTesting
-    internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
-
-    private val overlayViewParams = WindowManager.LayoutParams(
-        WindowManager.LayoutParams.WRAP_CONTENT,
-        WindowManager.LayoutParams.WRAP_CONTENT,
-        WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
-        Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
-        PixelFormat.TRANSLUCENT
-    ).apply {
-        title = TAG
-        fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
-        gravity = Gravity.TOP or Gravity.LEFT
-        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
-    }
-
-    init {
-        fingerprintManager?.setSidefpsController(
-            object : ISidefpsController.Stub() {
-                override fun show(
-                    sensorId: Int,
-                    @BiometricOverlayConstants.ShowReason reason: Int
-                ) = if (reason.isReasonToShow(activityTaskManager)) show() else hide()
-
-                override fun hide(sensorId: Int) = hide()
-            })
-        overviewProxyService.addCallback(overviewProxyListener)
-    }
-
-    /** Shows the side fps overlay if not already shown. */
-    fun show() {
-        mainExecutor.execute {
-            if (overlayView == null) {
-                createOverlayForDisplay()
-            } else {
-                Log.v(TAG, "overlay already shown")
-            }
-        }
-    }
-
-    /** Hides the fps overlay if shown. */
-    fun hide() {
-        mainExecutor.execute { overlayView = null }
-    }
-
-    private fun onOrientationChanged() {
-        if (overlayView != null) {
-            createOverlayForDisplay()
-        }
-    }
-
-    private fun createOverlayForDisplay() {
-        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
-        overlayView = view
-        val display = context.display!!
-        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
-            if (location == null) {
-                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
-            }
-            location ?: sensorProps.location
-        }
-        overlayOffsets = offsets
-
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
-        view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
-        lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
-        lottie.addLottieOnCompositionLoadedListener {
-            // Check that view is not stale, and that overlayView has not been hidden/removed
-            if (overlayView != null && overlayView == view) {
-                updateOverlayParams(display, it.bounds)
-            }
-        }
-        lottie.addOverlayDynamicColor(context)
-
-        /**
-         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
-         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator
-         * is in focus
-         */
-        view.setAccessibilityDelegate(object : AccessibilityDelegate() {
-            override fun dispatchPopulateAccessibilityEvent(
-                host: View,
-                event: AccessibilityEvent
-            ): Boolean {
-                return if (event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-                    true
-                } else {
-                    super.dispatchPopulateAccessibilityEvent(host, event)
-                }
-            }
-        })
-    }
-
-    @VisibleForTesting
-    internal fun updateOverlayParams(display: Display, bounds: Rect) {
-        val isNaturalOrientation = display.isNaturalOrientation()
-        val size = windowManager.maximumWindowMetrics.bounds
-        val displayWidth = if (isNaturalOrientation) size.width() else size.height()
-        val displayHeight = if (isNaturalOrientation) size.height() else size.width()
-        val boundsWidth = if (isNaturalOrientation) bounds.width() else bounds.height()
-        val boundsHeight = if (isNaturalOrientation) bounds.height() else bounds.width()
-        val sensorBounds = if (overlayOffsets.isYAligned()) {
-            Rect(
-                displayWidth - boundsWidth,
-                overlayOffsets.sensorLocationY,
-                displayWidth,
-                overlayOffsets.sensorLocationY + boundsHeight
-            )
-        } else {
-            Rect(
-                overlayOffsets.sensorLocationX,
-                0,
-                overlayOffsets.sensorLocationX + boundsWidth,
-                boundsHeight
-            )
-        }
-
-        RotationUtils.rotateBounds(
-            sensorBounds,
-            Rect(0, 0, displayWidth, displayHeight),
-            display.rotation
-        )
-
-        overlayViewParams.x = sensorBounds.left
-        overlayViewParams.y = sensorBounds.top
-        windowManager.updateViewLayout(overlayView, overlayViewParams)
-    }
-
-    private fun updateOverlayVisibility(view: View) {
-        if (view != overlayView) {
-            return
-        }
-        // hide after a few seconds if the sensor is oriented down and there are
-        // large overlapping system bars
-        val rotation = context.display?.rotation
-        if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
-            ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
-                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) {
-            overlayHideAnimator = view.animate()
-                .alpha(0f)
-                .setStartDelay(3_000)
-                .setDuration(animationDuration)
-                .setListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator) {
-                        view.visibility = View.GONE
-                        overlayHideAnimator = null
-                    }
-                })
-        } else {
-            overlayHideAnimator?.cancel()
-            overlayHideAnimator = null
-            view.alpha = 1f
-            view.visibility = View.VISIBLE
-        }
-    }
-}
-
-private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
-    get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
-
-/** Returns [True] when the device has a side fingerprint sensor. */
-fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
-
-@BiometricOverlayConstants.ShowReason
-private fun Int.isReasonToShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) {
-    REASON_AUTH_KEYGUARD -> false
-    REASON_AUTH_SETTINGS -> when (activityTaskManager.topClass()) {
-        // TODO(b/186176653): exclude fingerprint overlays from this list view
-        "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
-        else -> true
-    }
-    else -> true
-}
-
-private fun ActivityTaskManager.topClass(): String =
-    getTasks(1).firstOrNull()?.topActivity?.className ?: ""
-
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) {
-    Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-    Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
-    else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
-}
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) {
-    Surface.ROTATION_90 -> if (yAligned) 0f else 180f
-    Surface.ROTATION_180 -> 180f
-    Surface.ROTATION_270 -> if (yAligned) 180f else 0f
-    else -> 0f
-}
-
-private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
-
-private fun Display.isNaturalOrientation(): Boolean =
-    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
-private fun WindowInsets.hasBigNavigationBar(): Boolean =
-    getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
-
-private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
-    fun update() {
-        val c = context.getColor(R.color.biometric_dialog_accent)
-        for (key in listOf(".blue600", ".blue400")) {
-            addValueCallback(
-                KeyPath(key, "**"),
-                LottieProperty.COLOR_FILTER
-            ) { PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP) }
-        }
-    }
-
-    if (composition != null) {
-        update()
-    } else {
-        addLottieOnCompositionLoadedListener { update() }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a091ed9..5469d29 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -23,8 +23,6 @@
 import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,8 +30,11 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.Handler;
@@ -51,10 +52,14 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
 import com.android.systemui.dagger.SysUISingleton;
@@ -64,7 +69,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -79,6 +84,8 @@
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.time.SystemClock;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
@@ -101,7 +108,7 @@
  */
 @SuppressWarnings("deprecation")
 @SysUISingleton
-public class UdfpsController implements DozeReceiver {
+public class UdfpsController implements DozeReceiver, Dumpable {
     private static final String TAG = "UdfpsController";
     private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
 
@@ -135,11 +142,11 @@
     @NonNull private final LatencyTracker mLatencyTracker;
     @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
     @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    @NonNull private final BouncerInteractor mBouncerInteractor;
+    @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
-    @VisibleForTesting int mSensorId;
+    @VisibleForTesting @NonNull FingerprintSensorPropertiesInternal mSensorProps;
     @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -204,6 +211,11 @@
         }
     };
 
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mSensorProps=(" + mSensorProps + ")");
+    }
+
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@@ -223,7 +235,7 @@
                             mUdfpsDisplayMode, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
                                     fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
-                            mBouncerInteractor)));
+                            mPrimaryBouncerInteractor)));
         }
 
         @Override
@@ -259,7 +271,7 @@
                     }
                     mAcquiredReceived = true;
                     final UdfpsView view = mOverlay.getOverlayView();
-                    if (view != null) {
+                    if (view != null && isOptical()) {
                         unconfigureDisplay(view);
                     }
                     if (acquiredGood) {
@@ -329,26 +341,27 @@
     /**
      * Updates the overlay parameters and reconstructs or redraws the overlay, if necessary.
      *
-     * @param sensorId      sensor for which the overlay is getting updated.
+     * @param sensorProps   sensor for which the overlay is getting updated.
      * @param overlayParams See {@link UdfpsOverlayParams}.
      */
-    public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) {
-        if (sensorId != mSensorId) {
-            mSensorId = sensorId;
+    public void updateOverlayParams(@NonNull FingerprintSensorPropertiesInternal sensorProps,
+            @NonNull UdfpsOverlayParams overlayParams) {
+        if (mSensorProps.sensorId != sensorProps.sensorId) {
+            mSensorProps = sensorProps;
             Log.w(TAG, "updateUdfpsParams | sensorId has changed");
         }
 
         if (!mOverlayParams.equals(overlayParams)) {
             mOverlayParams = overlayParams;
 
-            final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
+            final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer();
 
             // When the bounds change it's always necessary to re-create the overlay's window with
             // new LayoutParams. If the overlay needs to be shown, this will re-create and show the
             // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden.
             redrawOverlay();
             if (wasShowingAltAuth) {
-                mKeyguardViewManager.showGenericBouncer(true);
+                mKeyguardViewManager.showBouncer(true);
             }
         }
     }
@@ -358,7 +371,7 @@
         mAuthControllerUpdateUdfpsLocation = r;
     }
 
-    public void setUdfpsDisplayMode(UdfpsDisplayModeProvider udfpsDisplayMode) {
+    public void setUdfpsDisplayMode(@NonNull UdfpsDisplayModeProvider udfpsDisplayMode) {
         mUdfpsDisplayMode = udfpsDisplayMode;
     }
 
@@ -462,7 +475,6 @@
         }
 
         final UdfpsView udfpsView = mOverlay.getOverlayView();
-        final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
         boolean handled = false;
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_OUTSIDE:
@@ -546,15 +558,14 @@
                                 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
                                 minor, major, v, exceedsVelocityThreshold);
                         final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
-                        if (!isDisplayConfigured && !mAcquiredReceived
-                                && !exceedsVelocityThreshold) {
 
+                        if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) {
                             final float scale = mOverlayParams.getScaleFactor();
                             float scaledMinor = minor / scale;
                             float scaledMajor = major / scale;
-
                             onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor,
                                     scaledMajor);
+
                             Log.v(TAG, "onTouch | finger down: " + touchInfo);
                             mTouchLogTime = mSystemClock.elapsedRealtime();
                             handled = true;
@@ -648,8 +659,8 @@
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
             @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
-            @BiometricsBackground Executor biometricsExecutor,
-            @NonNull BouncerInteractor bouncerInteractor) {
+            @NonNull @BiometricsBackground Executor biometricsExecutor,
+            @NonNull PrimaryBouncerInteractor primaryBouncerInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -679,8 +690,18 @@
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
         mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+        mSensorProps = new FingerprintSensorPropertiesInternal(
+                -1 /* sensorId */,
+                SensorProperties.STRENGTH_CONVENIENCE,
+                0 /* maxEnrollmentsPerUser */,
+                new ArrayList<>() /* componentInfo */,
+                FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+
         mBiometricExecutor = biometricsExecutor;
-        mBouncerInteractor = bouncerInteractor;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
+
+        mDumpManager.registerDumpable(TAG, this);
 
         mOrientationListener = new BiometricDisplayListener(
                 context,
@@ -770,8 +791,8 @@
                 onFingerUp(mOverlay.getRequestId(), oldView);
             }
             final boolean removed = mOverlay.hide();
-            if (mKeyguardViewManager.isShowingAlternateAuth()) {
-                mKeyguardViewManager.resetAlternateAuth(true);
+            if (mKeyguardViewManager.isShowingAlternateBouncer()) {
+                mKeyguardViewManager.hideAlternateBouncer(true);
             }
             Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed);
         } else {
@@ -811,7 +832,7 @@
                 Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
                 return;
             }
-            mKeyguardViewManager.showBouncer(true);
+            mKeyguardViewManager.showPrimaryBouncer(true);
 
             // play the same haptic as the LockIconViewController longpress
             mVibrator.vibrate(
@@ -877,6 +898,10 @@
         mIsAodInterruptActive = false;
     }
 
+    private boolean isOptical() {
+        return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+    }
+
     public boolean isFingerDown() {
         return mOnFingerDown;
     }
@@ -893,7 +918,9 @@
                     + " current: " + mOverlay.getRequestId());
             return;
         }
-        mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+        if (isOptical()) {
+            mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+        }
         // Refresh screen timeout and boost process priority if possible.
         mPowerManager.userActivity(mSystemClock.uptimeMillis(),
                 PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
@@ -916,11 +943,11 @@
                 }
             });
         } else {
-            mFingerprintManager.onPointerDown(requestId, mSensorId, x, y, minor, major);
+            mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major);
         }
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
         final UdfpsView view = mOverlay.getOverlayView();
-        if (view != null) {
+        if (view != null && isOptical()) {
             view.configureDisplay(() -> {
                 if (mAlternateTouchProvider != null) {
                     mBiometricExecutor.execute(() -> {
@@ -928,7 +955,7 @@
                         mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                     });
                 } else {
-                    mFingerprintManager.onUiReady(requestId, mSensorId);
+                    mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
                     mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                 }
             });
@@ -954,15 +981,16 @@
                     }
                 });
             } else {
-                mFingerprintManager.onPointerUp(requestId, mSensorId);
+                mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
             }
             for (Callback cb : mCallbacks) {
                 cb.onFingerUp();
             }
         }
         mOnFingerDown = false;
-        unconfigureDisplay(view);
-
+        if (isOptical()) {
+            unconfigureDisplay(view);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index d70861a..0bb24f8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -49,7 +49,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -95,7 +95,7 @@
         private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
         private val activityLaunchAnimator: ActivityLaunchAnimator,
         private val featureFlags: FeatureFlags,
-        private val bouncerInteractor: BouncerInteractor,
+        private val primaryBouncerInteractor: PrimaryBouncerInteractor,
         private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
 ) {
     /** The view, when [isShowing], or null. */
@@ -252,7 +252,7 @@
                     controller,
                     activityLaunchAnimator,
                     featureFlags,
-                    bouncerInteractor
+                    primaryBouncerInteractor
                 )
             }
             REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 5bae2dc..91967f9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
@@ -40,9 +40,9 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.KeyguardBouncer
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateBouncer
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
@@ -73,7 +73,7 @@
     private val udfpsController: UdfpsController,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
     featureFlags: FeatureFlags,
-    private val bouncerInteractor: BouncerInteractor
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardView>(
         view,
@@ -146,8 +146,8 @@
             }
         }
 
-    private val bouncerExpansionCallback: BouncerExpansionCallback =
-        object : BouncerExpansionCallback {
+    private val mPrimaryBouncerExpansionCallback: PrimaryBouncerExpansionCallback =
+        object : PrimaryBouncerExpansionCallback {
             override fun onExpansionChanged(expansion: Float) {
                 inputBouncerHiddenAmount = expansion
                 updateAlpha()
@@ -180,7 +180,7 @@
 
     private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
         panelExpansionFraction =
-            if (keyguardViewManager.isBouncerInTransit) {
+            if (keyguardViewManager.isPrimaryBouncerInTransit) {
                 aboutToShowBouncerProgress(fraction)
             } else {
                 fraction
@@ -237,17 +237,17 @@
             }
         }
 
-    private val alternateAuthInterceptor: AlternateAuthInterceptor =
-        object : AlternateAuthInterceptor {
-            override fun showAlternateAuthBouncer(): Boolean {
+    private val mAlternateBouncer: AlternateBouncer =
+        object : AlternateBouncer {
+            override fun showAlternateBouncer(): Boolean {
                 return showUdfpsBouncer(true)
             }
 
-            override fun hideAlternateAuthBouncer(): Boolean {
+            override fun hideAlternateBouncer(): Boolean {
                 return showUdfpsBouncer(false)
             }
 
-            override fun isShowingAlternateAuthBouncer(): Boolean {
+            override fun isShowingAlternateBouncer(): Boolean {
                 return showingUdfpsBouncer
             }
 
@@ -268,7 +268,7 @@
 
     override fun onInit() {
         super.onInit()
-        keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+        keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
     }
 
     init {
@@ -285,7 +285,7 @@
     @VisibleForTesting
     internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
         return scope.launch {
-            bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+            primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
                 inputBouncerExpansion = bouncerExpansion
                 updateAlpha()
                 updatePauseAuth()
@@ -306,10 +306,10 @@
         qsExpansion = keyguardViewManager.qsExpansion
         keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
         if (!isModernBouncerEnabled) {
-            val bouncer = keyguardViewManager.bouncer
+            val bouncer = keyguardViewManager.primaryBouncer
             bouncer?.expansion?.let {
-                bouncerExpansionCallback.onExpansionChanged(it)
-                bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+                mPrimaryBouncerExpansionCallback.onExpansionChanged(it)
+                bouncer.addBouncerExpansionCallback(mPrimaryBouncerExpansionCallback)
             }
             updateBouncerHiddenAmount()
         }
@@ -319,7 +319,7 @@
         view.updatePadding()
         updateAlpha()
         updatePauseAuth()
-        keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+        keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
         lockScreenShadeTransitionController.udfpsKeyguardViewController = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
     }
@@ -329,7 +329,7 @@
         faceDetectRunning = false
         keyguardStateController.removeCallback(keyguardStateControllerCallback)
         statusBarStateController.removeCallback(stateListener)
-        keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+        keyguardViewManager.removeAlternateAuthInterceptor(mAlternateBouncer)
         keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         configurationController.removeCallback(configurationListener)
         shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
@@ -339,7 +339,9 @@
         activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
         keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
         if (!isModernBouncerEnabled) {
-            keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+            keyguardViewManager.primaryBouncer?.removeBouncerExpansionCallback(
+                mPrimaryBouncerExpansionCallback
+            )
         }
     }
 
@@ -442,7 +444,7 @@
         return if (isModernBouncerEnabled) {
             inputBouncerExpansion == 1f
         } else {
-            keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+            keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateBouncer
         }
     }
 
@@ -462,7 +464,7 @@
      */
     private fun maybeShowInputBouncer() {
         if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
-            keyguardViewManager.showBouncer(true)
+            keyguardViewManager.showPrimaryBouncer(true)
         }
     }
 
@@ -535,8 +537,8 @@
         if (isModernBouncerEnabled) {
             return
         }
-        val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
-        if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+        val altBouncerShowing = keyguardViewManager.isShowingAlternateBouncer
+        if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
             inputBouncerHiddenAmount = 1f
         } else if (keyguardViewManager.isBouncerShowing) {
             // input bouncer is fully showing
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 5850c95..08c7c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -127,7 +127,10 @@
                 action,
                 userId,
                 {
-                    Trace.beginSection("registerReceiver act=$action user=$userId")
+                    if (Trace.isEnabled()) {
+                        Trace.traceBegin(
+                                Trace.TRACE_TAG_APP, "registerReceiver act=$action user=$userId")
+                    }
                     context.registerReceiverAsUser(
                             this,
                             UserHandle.of(userId),
@@ -141,7 +144,11 @@
                 },
                 {
                     try {
-                        Trace.beginSection("unregisterReceiver act=$action user=$userId")
+                        if (Trace.isEnabled()) {
+                            Trace.traceBegin(
+                                    Trace.TRACE_TAG_APP,
+                                    "unregisterReceiver act=$action user=$userId")
+                        }
                         context.unregisterReceiver(this)
                         Trace.endSection()
                         logger.logContextReceiverUnregistered(userId, action)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 2245d84..beaccba 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -34,6 +34,8 @@
 import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
 import com.android.systemui.classifier.HistoryTracker.BeliefListener;
 import com.android.systemui.dagger.qualifiers.TestHarness;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -65,6 +67,7 @@
     private static final double FALSE_BELIEF_THRESHOLD = 0.9;
 
     private final FalsingDataProvider mDataProvider;
+    private final LongTapClassifier mLongTapClassifier;
     private final SingleTapClassifier mSingleTapClassifier;
     private final DoubleTapClassifier mDoubleTapClassifier;
     private final HistoryTracker mHistoryTracker;
@@ -73,6 +76,7 @@
     private final boolean mTestHarness;
     private final MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
+    private FeatureFlags mFeatureFlags;
     private static final Queue<String> RECENT_INFO_LOG =
             new ArrayDeque<>(RECENT_INFO_LOG_SIZE + 1);
     private static final Queue<DebugSwipeRecord> RECENT_SWIPES =
@@ -175,19 +179,23 @@
     public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
             MetricsLogger metricsLogger,
             @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
-            SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
-            HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
+            SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier,
+            DoubleTapClassifier doubleTapClassifier, HistoryTracker historyTracker,
+            KeyguardStateController keyguardStateController,
             AccessibilityManager accessibilityManager,
-            @TestHarness boolean testHarness) {
+            @TestHarness boolean testHarness,
+            FeatureFlags featureFlags) {
         mDataProvider = falsingDataProvider;
         mMetricsLogger = metricsLogger;
         mClassifiers = classifiers;
         mSingleTapClassifier = singleTapClassifier;
+        mLongTapClassifier = longTapClassifier;
         mDoubleTapClassifier = doubleTapClassifier;
         mHistoryTracker = historyTracker;
         mKeyguardStateController = keyguardStateController;
         mAccessibilityManager = accessibilityManager;
         mTestHarness = testHarness;
+        mFeatureFlags = featureFlags;
 
         mDataProvider.addSessionListener(mSessionListener);
         mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
@@ -313,6 +321,58 @@
     }
 
     @Override
+    public boolean isFalseLongTap(@Penalty int penalty) {
+        if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) {
+            return false;
+        }
+
+        checkDestroyed();
+
+        if (skipFalsing(GENERIC)) {
+            mPriorResults = getPassedResult(1);
+            logDebug("Skipped falsing");
+            return false;
+        }
+
+        double falsePenalty = 0;
+        switch(penalty) {
+            case NO_PENALTY:
+                falsePenalty = 0;
+                break;
+            case LOW_PENALTY:
+                falsePenalty = 0.1;
+                break;
+            case MODERATE_PENALTY:
+                falsePenalty = 0.3;
+                break;
+            case HIGH_PENALTY:
+                falsePenalty = 0.6;
+                break;
+        }
+
+        FalsingClassifier.Result longTapResult =
+                mLongTapClassifier.isTap(mDataProvider.getRecentMotionEvents().isEmpty()
+                        ? mDataProvider.getPriorMotionEvents()
+                        : mDataProvider.getRecentMotionEvents(), falsePenalty);
+        mPriorResults = Collections.singleton(longTapResult);
+
+        if (!longTapResult.isFalse()) {
+            if (mDataProvider.isJustUnlockedWithFace()) {
+                // Immediately pass if a face is detected.
+                mPriorResults = getPassedResult(1);
+                logDebug("False Long Tap: false (face detected)");
+            } else {
+                mPriorResults = getPassedResult(0.1);
+                logDebug("False Long Tap: false (default)");
+            }
+            return false;
+        } else {
+            logDebug("False Long Tap: " + longTapResult.isFalse() + " (simple)");
+            return longTapResult.isFalse();
+        }
+    }
+
+    @Override
     public boolean isFalseDoubleTap() {
         checkDestroyed();
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 5d04b5f..c4723e8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -139,6 +139,11 @@
     }
 
     @Override
+    public boolean isFalseLongTap(int penalty) {
+        return mInternalFalsingManager.isFalseLongTap(penalty);
+    }
+
+    @Override
     public boolean isFalseDoubleTap() {
         return mInternalFalsingManager.isFalseDoubleTap();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index 7b7f17e..5302af9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -40,6 +40,7 @@
 public interface FalsingModule {
     String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers";
     String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop";
+    String LONG_TAP_TOUCH_SLOP = "falsing_long_tap_slop";
     String DOUBLE_TAP_TOUCH_SLOP = "falsing_double_tap_touch_slop";
     String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
 
@@ -81,4 +82,11 @@
     static float providesSingleTapTouchSlop(ViewConfiguration viewConfiguration) {
         return viewConfiguration.getScaledTouchSlop();
     }
+
+    /** */
+    @Provides
+    @Named(LONG_TAP_TOUCH_SLOP)
+    static float providesLongTapTouchSlop(ViewConfiguration viewConfiguration) {
+        return viewConfiguration.getScaledTouchSlop() * 1.25f;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java
new file mode 100644
index 0000000..1963e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.classifier;
+
+import static com.android.systemui.classifier.FalsingModule.LONG_TAP_TOUCH_SLOP;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Falsing classifier that accepts or rejects a gesture as a long tap.
+ */
+public class LongTapClassifier extends TapClassifier{
+
+    @Inject
+    LongTapClassifier(FalsingDataProvider dataProvider,
+            @Named(LONG_TAP_TOUCH_SLOP) float touchSlop) {
+        super(dataProvider, touchSlop);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
index bd6fbfb..7a7401d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
@@ -18,57 +18,17 @@
 
 import static com.android.systemui.classifier.FalsingModule.SINGLE_TAP_TOUCH_SLOP;
 
-import android.view.MotionEvent;
-
-import java.util.List;
-
 import javax.inject.Inject;
 import javax.inject.Named;
 
 /**
- * Falsing classifier that accepts or rejects a single gesture as a tap.
+ * Falsing classifier that accepts or rejects a gesture as a single tap.
  */
-public class SingleTapClassifier extends FalsingClassifier {
-    private final float mTouchSlop;
+public class SingleTapClassifier extends TapClassifier {
 
     @Inject
     SingleTapClassifier(FalsingDataProvider dataProvider,
             @Named(SINGLE_TAP_TOUCH_SLOP) float touchSlop) {
-        super(dataProvider);
-        mTouchSlop = touchSlop;
-    }
-
-    @Override
-    Result calculateFalsingResult(
-            @Classifier.InteractionType int interactionType,
-            double historyBelief, double historyConfidence) {
-        return isTap(getRecentMotionEvents(), 0.5);
-    }
-
-    /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
-    public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
-        if (motionEvents.isEmpty()) {
-            return falsed(0, "no motion events");
-        }
-        float downX = motionEvents.get(0).getX();
-        float downY = motionEvents.get(0).getY();
-
-        for (MotionEvent event : motionEvents) {
-            String reason;
-            if (Math.abs(event.getX() - downX) >= mTouchSlop) {
-                reason = "dX too big for a tap: "
-                        + Math.abs(event.getX() - downX)
-                        + "vs "
-                        + mTouchSlop;
-                return falsed(falsePenalty, reason);
-            } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
-                reason = "dY too big for a tap: "
-                        + Math.abs(event.getY() - downY)
-                        + " vs "
-                        + mTouchSlop;
-                return falsed(falsePenalty, reason);
-            }
-        }
-        return Result.passed(0);
+        super(dataProvider, touchSlop);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java
new file mode 100644
index 0000000..e24cfaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.classifier;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+
+/**
+ * Falsing classifier that accepts or rejects a gesture as a tap.
+ */
+public abstract class TapClassifier extends FalsingClassifier{
+    private final float mTouchSlop;
+
+    TapClassifier(FalsingDataProvider dataProvider,
+            float touchSlop) {
+        super(dataProvider);
+        mTouchSlop = touchSlop;
+    }
+
+    @Override
+    Result calculateFalsingResult(
+            @Classifier.InteractionType int interactionType,
+            double historyBelief, double historyConfidence) {
+        return isTap(getRecentMotionEvents(), 0.5);
+    }
+
+    /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
+    public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
+        if (motionEvents.isEmpty()) {
+            return falsed(0, "no motion events");
+        }
+        float downX = motionEvents.get(0).getX();
+        float downY = motionEvents.get(0).getY();
+
+        for (MotionEvent event : motionEvents) {
+            String reason;
+            if (Math.abs(event.getX() - downX) >= mTouchSlop) {
+                reason = "dX too big for a tap: "
+                        + Math.abs(event.getX() - downX)
+                        + "vs "
+                        + mTouchSlop;
+                return falsed(falsePenalty, reason);
+            } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
+                reason = "dY too big for a tap: "
+                        + Math.abs(event.getY() - downY)
+                        + " vs "
+                        + mTouchSlop;
+                return falsed(falsePenalty, reason);
+            }
+        }
+        return Result.passed(0);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index fb01691..4eb444e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -25,6 +25,7 @@
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
+import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
 import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -142,4 +143,11 @@
     @ClassKey(HdmiCecSetMenuLanguageActivity.class)
     public abstract Activity bindHdmiCecSetMenuLanguageActivity(
             HdmiCecSetMenuLanguageActivity activity);
+
+    /** Inject into TvSensorPrivacyChangedActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(TvSensorPrivacyChangedActivity.class)
+    public abstract Activity bindTvSensorPrivacyChangedActivity(
+            TvSensorPrivacyChangedActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..25418c3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.INotificationManager;
@@ -137,6 +138,12 @@
 
     @Provides
     @Singleton
+    static AppOpsManager provideAppOpsManager(Context context) {
+        return context.getSystemService(AppOpsManager.class);
+    }
+
+    @Provides
+    @Singleton
     static AudioManager provideAudioManager(Context context) {
         return context.getSystemService(AudioManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 9e33ee1..fe89c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -27,7 +27,9 @@
 import dagger.Component;
 
 /**
- * Root component for Dagger injection.
+ * Base root component for Dagger injection.
+ *
+ * See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
  */
 @Singleton
 @Component(modules = {GlobalModule.class})
@@ -51,7 +53,7 @@
     WMComponent.Builder getWMComponentBuilder();
 
     /**
-     * Builder for a {@link SysUIComponent}, which makes it a subcomponent of this class.
+     * Builder for a {@link ReferenceSysUIComponent}, which makes it a subcomponent of this class.
      */
     SysUIComponent.Builder getSysUIComponent();
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java
new file mode 100644
index 0000000..be93c9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection used in AOSP.
+ */
+@Singleton
+@Component(modules = {GlobalModule.class})
+public interface ReferenceGlobalRootComponent extends GlobalRootComponent {
+
+    /**
+     * Builder for a ReferenceGlobalRootComponent.
+     */
+    @Component.Builder
+    interface Builder extends GlobalRootComponent.Builder {
+        ReferenceGlobalRootComponent build();
+    }
+
+    /**
+     * Builder for a {@link ReferenceSysUIComponent}, which makes it a subcomponent of this class.
+     */
+    ReferenceSysUIComponent.Builder getSysUIComponent();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
new file mode 100644
index 0000000..7ab36e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import com.android.systemui.statusbar.QsFrameTranslateModule;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for Core SysUI used in AOSP.
+ */
+@SysUISingleton
+@Subcomponent(modules = {
+        DefaultComponentBinder.class,
+        DependencyProvider.class,
+        QsFrameTranslateModule.class,
+        SystemUIBinder.class,
+        SystemUIModule.class,
+        SystemUICoreStartableModule.class,
+        ReferenceSystemUIModule.class})
+public interface ReferenceSysUIComponent extends SysUIComponent {
+
+    /**
+     * Builder for a ReferenceSysUIComponent.
+     */
+    @SysUISingleton
+    @Subcomponent.Builder
+    interface Builder extends SysUIComponent.Builder {
+        ReferenceSysUIComponent build();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d05bd51..a14b0ee 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -40,7 +40,6 @@
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
@@ -58,7 +57,9 @@
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for Core SysUI.
+ * An example Dagger Subcomponent for Core SysUI.
+ *
+ * See {@link ReferenceSysUIComponent} for the one actually used by AOSP.
  */
 @SysUISingleton
 @Subcomponent(modules = {
@@ -111,9 +112,6 @@
         Builder setBackAnimation(Optional<BackAnimation> b);
 
         @BindsInstance
-        Builder setFloatingTasks(Optional<FloatingTasks> f);
-
-        @BindsInstance
         Builder setDesktopMode(Optional<DesktopMode> d);
 
         SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 482bdaf..bcf5e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,12 +41,14 @@
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.data.BouncerViewModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.motiontool.MotionToolModule;
 import com.android.systemui.navigationbar.NavigationBarComponent;
 import com.android.systemui.notetask.NoteTaskModule;
 import com.android.systemui.people.PeopleModule;
@@ -133,6 +135,7 @@
             FooterActionsModule.class,
             LogModule.class,
             MediaProjectionModule.class,
+            MotionToolModule.class,
             PeopleHubModule.class,
             PeopleModule.class,
             PluginModule.class,
@@ -238,6 +241,7 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
+            FeatureFlags featureFlags,
             @Main Executor sysuiMainExecutor) {
         return Optional.ofNullable(BubblesManager.create(context,
                 bubblesOptional,
@@ -254,6 +258,7 @@
                 notifCollection,
                 notifPipeline,
                 sysUiState,
+                featureFlags,
                 sysuiMainExecutor));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 096f969..d756f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -32,7 +32,6 @@
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.floating.FloatingTasks;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
@@ -111,9 +110,6 @@
     @WMSingleton
     Optional<BackAnimation> getBackAnimation();
 
-    @WMSingleton
-    Optional<FloatingTasks> getFloatingTasks();
-
     /**
      * Optional {@link DesktopMode} component for interacting with desktop mode.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 60227ee..937884c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -171,7 +171,10 @@
 
     @Override
     public void onSensorChanged(SensorEvent event) {
-        Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, "DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        }
         try {
             if (mRegistered) {
                 mLastSensorValue = (int) event.values[0];
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
new file mode 100644
index 0000000..d8dd6a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for dream overlay animations. */
+class DreamOverlayAnimationsController
+@Inject
+constructor(
+    private val mBlurUtils: BlurUtils,
+    private val mComplicationHostViewController: ComplicationHostViewController,
+    private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+    private val mOverlayStateController: DreamOverlayStateController,
+    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
+    private val mDreamInBlurAnimDuration: Int,
+    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+    @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+    private val mDreamInComplicationsAnimDuration: Int,
+    @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+    private val mDreamInTopComplicationsAnimDelay: Int,
+    @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+    private val mDreamInBottomComplicationsAnimDelay: Int
+) {
+
+    var mEntryAnimations: AnimatorSet? = null
+
+    /** Starts the dream content and dream overlay entry animations. */
+    fun startEntryAnimations(view: View) {
+        cancelRunningEntryAnimations()
+
+        mEntryAnimations = AnimatorSet()
+        mEntryAnimations?.apply {
+            playTogether(
+                buildDreamInBlurAnimator(view),
+                buildDreamInTopComplicationsAnimator(),
+                buildDreamInBottomComplicationsAnimator()
+            )
+            doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
+            start()
+        }
+    }
+
+    /** Cancels the dream content and dream overlay animations, if they're currently running. */
+    fun cancelRunningEntryAnimations() {
+        if (mEntryAnimations?.isRunning == true) {
+            mEntryAnimations?.cancel()
+        }
+        mEntryAnimations = null
+    }
+
+    private fun buildDreamInBlurAnimator(view: View): Animator {
+        return ValueAnimator.ofFloat(1f, 0f).apply {
+            duration = mDreamInBlurAnimDuration.toLong()
+            startDelay = mDreamInBlurAnimDelay.toLong()
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { animator: ValueAnimator ->
+                mBlurUtils.applyBlur(
+                    view.viewRootImpl,
+                    mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
+                    false /*opaque*/
+                )
+            }
+        }
+    }
+
+    private fun buildDreamInTopComplicationsAnimator(): Animator {
+        return ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = mDreamInComplicationsAnimDuration.toLong()
+            startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { va: ValueAnimator ->
+                setTopElementsAlpha(va.animatedValue as Float)
+            }
+        }
+    }
+
+    private fun buildDreamInBottomComplicationsAnimator(): Animator {
+        return ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = mDreamInComplicationsAnimDuration.toLong()
+            startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { va: ValueAnimator ->
+                setBottomElementsAlpha(va.animatedValue as Float)
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator) {
+                        mComplicationHostViewController
+                            .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+                            .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
+                    }
+                }
+            )
+        }
+    }
+
+    /** Sets alpha of top complications and the status bar. */
+    private fun setTopElementsAlpha(alpha: Float) {
+        mComplicationHostViewController
+            .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
+            .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+        mStatusBarViewController.setAlpha(alpha)
+    }
+
+    /** Sets alpha of bottom complications. */
+    private fun setBottomElementsAlpha(alpha: Float) {
+        mComplicationHostViewController
+            .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+            .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+    }
+
+    private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
+        if (alpha > 0 && view.visibility != View.VISIBLE) {
+            view.visibility = View.VISIBLE
+        }
+
+        view.alpha = alpha
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80d..5c6d248 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -35,7 +35,7 @@
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -54,6 +54,8 @@
     private final DreamOverlayStatusBarViewController mStatusBarViewController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final BlurUtils mBlurUtils;
+    private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
+    private final DreamOverlayStateController mStateController;
 
     private final ComplicationHostViewController mComplicationHostViewController;
 
@@ -74,14 +76,14 @@
     // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
     private final Handler mHandler;
     private final int mDreamOverlayMaxTranslationY;
-    private final BouncerCallbackInteractor mBouncerCallbackInteractor;
+    private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
 
     private long mJitterStartTimeMillis;
 
     private boolean mBouncerAnimating;
 
-    private final KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback =
-            new KeyguardBouncer.BouncerExpansionCallback() {
+    private final KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback =
+            new KeyguardBouncer.PrimaryBouncerExpansionCallback() {
 
                 @Override
                 public void onStartingToShow() {
@@ -134,12 +136,16 @@
             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
                     burnInProtectionUpdateInterval,
             @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
-            BouncerCallbackInteractor bouncerCallbackInteractor) {
+            PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
+            DreamOverlayAnimationsController animationsController,
+            DreamOverlayStateController stateController) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mBlurUtils = blurUtils;
+        mDreamOverlayAnimationsController = animationsController;
+        mStateController = stateController;
 
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
@@ -154,7 +160,7 @@
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
         mMillisUntilFullJitter = millisUntilFullJitter;
-        mBouncerCallbackInteractor = bouncerCallbackInteractor;
+        mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
     }
 
     @Override
@@ -167,21 +173,28 @@
     protected void onViewAttached() {
         mJitterStartTimeMillis = System.currentTimeMillis();
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
-        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
+        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
         if (bouncer != null) {
             bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
         }
-        mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+
+        // Start dream entry animations. Skip animations for low light clock.
+        if (!mStateController.isLowLightActive()) {
+            mDreamOverlayAnimationsController.startEntryAnimations(mView);
+        }
     }
 
     @Override
     protected void onViewDetached() {
         mHandler.removeCallbacks(this::updateBurnInOffsets);
-        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getBouncer();
+        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
         if (bouncer != null) {
             bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         }
-        mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+        mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+
+        mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
     }
 
     View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d1b7368..8542412 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -90,13 +90,15 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onShadeExpandedChanged(boolean expanded) {
-                    if (mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED
-                            && mLifecycleRegistry.getCurrentState() != Lifecycle.State.STARTED) {
-                        return;
-                    }
+                    mExecutor.execute(() -> {
+                        if (getCurrentStateLocked() != Lifecycle.State.RESUMED
+                                && getCurrentStateLocked() != Lifecycle.State.STARTED) {
+                            return;
+                        }
 
-                    mLifecycleRegistry.setCurrentState(
-                            expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+                        setCurrentStateLocked(
+                                expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+                    });
                 }
             };
 
@@ -146,29 +148,30 @@
                 () -> mExecutor.execute(DreamOverlayService.this::requestExit);
         mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
         mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
-        setCurrentState(Lifecycle.State.CREATED);
-    }
 
-    private void setCurrentState(Lifecycle.State state) {
-        mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
+        mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
     }
 
     @Override
     public void onDestroy() {
         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
-        setCurrentState(Lifecycle.State.DESTROYED);
 
-        resetCurrentDreamOverlay();
+        mExecutor.execute(() -> {
+            setCurrentStateLocked(Lifecycle.State.DESTROYED);
 
-        mDestroyed = true;
+            resetCurrentDreamOverlayLocked();
+
+            mDestroyed = true;
+        });
+
         super.onDestroy();
     }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
-        setCurrentState(Lifecycle.State.STARTED);
-
         mExecutor.execute(() -> {
+            setCurrentStateLocked(Lifecycle.State.STARTED);
+
             mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
 
             if (mDestroyed) {
@@ -181,7 +184,7 @@
                 // Reset the current dream overlay before starting a new one. This can happen
                 // when two dreams overlap (briefly, for a smoother dream transition) and both
                 // dreams are bound to the dream overlay service.
-                resetCurrentDreamOverlay();
+                resetCurrentDreamOverlayLocked();
             }
 
             mDreamOverlayContainerViewController =
@@ -191,7 +194,7 @@
 
             mStateController.setShouldShowComplications(shouldShowComplications());
             addOverlayWindowLocked(layoutParams);
-            setCurrentState(Lifecycle.State.RESUMED);
+            setCurrentStateLocked(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
             final ComponentName dreamComponent = getDreamComponent();
             mStateController.setLowLightActive(
@@ -202,6 +205,14 @@
         });
     }
 
+    private Lifecycle.State getCurrentStateLocked() {
+        return mLifecycleRegistry.getCurrentState();
+    }
+
+    private void setCurrentStateLocked(Lifecycle.State state) {
+        mLifecycleRegistry.setCurrentState(state);
+    }
+
     /**
      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
      * called from the main executing thread. The window attributes closely mirror those that are
@@ -231,13 +242,13 @@
         // Make extra sure the container view has been removed from its old parent (otherwise we
         // risk an IllegalStateException in some cases when setting the container view as the
         // window's content view and the container view hasn't been properly removed previously).
-        removeContainerViewFromParent();
+        removeContainerViewFromParentLocked();
         mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
 
         mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
     }
 
-    private void removeContainerViewFromParent() {
+    private void removeContainerViewFromParentLocked() {
         View containerView = mDreamOverlayContainerViewController.getContainerView();
         if (containerView == null) {
             return;
@@ -250,13 +261,14 @@
         parentView.removeView(containerView);
     }
 
-    private void resetCurrentDreamOverlay() {
+    private void resetCurrentDreamOverlayLocked() {
         if (mStarted && mWindow != null) {
             mWindowManager.removeView(mWindow.getDecorView());
         }
 
         mStateController.setOverlayActive(false);
         mStateController.setLowLightActive(false);
+        mStateController.setEntryAnimationsFinished(false);
 
         mDreamOverlayContainerViewController = null;
         mDreamOverlayTouchMonitor = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 72feaca..e80d0be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -51,6 +51,7 @@
 
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
+    public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
 
     private static final int OP_CLEAR_STATE = 1;
     private static final int OP_SET_STATE = 2;
@@ -202,6 +203,14 @@
         return containsState(STATE_LOW_LIGHT_ACTIVE);
     }
 
+    /**
+     * Returns whether the dream content and dream overlay entry animations are finished.
+     * @return {@code true} if animations are finished, {@code false} otherwise.
+     */
+    public boolean areEntryAnimationsFinished() {
+        return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+    }
+
     private boolean containsState(int state) {
         return (mState & state) != 0;
     }
@@ -218,7 +227,7 @@
         }
 
         if (existingState != mState) {
-            notifyCallbacks(callback -> callback.onStateChanged());
+            notifyCallbacks(Callback::onStateChanged);
         }
     }
 
@@ -239,6 +248,15 @@
     }
 
     /**
+     * Sets whether dream content and dream overlay entry animations are finished.
+     * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
+     */
+    public void setEntryAnimationsFinished(boolean finished) {
+        modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
+                STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+    }
+
+    /**
      * Returns the available complication types.
      */
     @Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index bb1c430..d17fbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,10 +16,6 @@
 
 package com.android.systemui.dreams;
 
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
 import android.app.AlarmManager;
 import android.app.StatusBarManager;
 import android.content.res.Resources;
@@ -83,6 +79,9 @@
 
     private boolean mIsAttached;
 
+    // Whether dream entry animations are finished.
+    private boolean mEntryAnimationsFinished = false;
+
     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
             .clearCapabilities()
             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -109,7 +108,9 @@
             new DreamOverlayStateController.Callback() {
                 @Override
                 public void onStateChanged() {
-                    updateLowLightState();
+                    mEntryAnimationsFinished =
+                            mDreamOverlayStateController.areEntryAnimationsFinished();
+                    updateVisibility();
                 }
             };
 
@@ -195,7 +196,6 @@
         mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
 
         mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
-        updateLowLightState();
 
         mTouchInsetSession.addViewToTracking(mView);
     }
@@ -216,6 +216,26 @@
         mIsAttached = false;
     }
 
+    /**
+     * Sets alpha of the dream overlay status bar.
+     *
+     * No-op if the dream overlay status bar should not be shown.
+     */
+    protected void setAlpha(float alpha) {
+        updateVisibility();
+
+        if (mView.getVisibility() != View.VISIBLE) {
+            return;
+        }
+
+        mView.setAlpha(alpha);
+    }
+
+    private boolean shouldShowStatusBar() {
+        return !mDreamOverlayStateController.isLowLightActive()
+                && !mStatusBarWindowStateController.windowIsShowing();
+    }
+
     private void updateWifiUnavailableStatusIcon() {
         final NetworkCapabilities capabilities =
                 mConnectivityManager.getNetworkCapabilities(
@@ -235,13 +255,12 @@
                 hasAlarm ? buildAlarmContentDescription(alarm) : null);
     }
 
-    private void updateLowLightState() {
-        int visibility = View.VISIBLE;
-        if (mDreamOverlayStateController.isLowLightActive()
-                || mStatusBarWindowStateController.windowIsShowing()) {
-            visibility = View.INVISIBLE;
+    private void updateVisibility() {
+        if (shouldShowStatusBar()) {
+            mView.setVisibility(View.VISIBLE);
+        } else {
+            mView.setVisibility(View.INVISIBLE);
         }
-        mView.setVisibility(visibility);
     }
 
     private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
@@ -298,21 +317,11 @@
     }
 
     private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
-        mMainExecutor.execute(() -> {
-            if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
-                return;
-            }
+        if (!mIsAttached || !mEntryAnimationsFinished) {
+            return;
+        }
 
-            switch (state) {
-                case WINDOW_STATE_SHOWING:
-                    mView.setVisibility(View.INVISIBLE);
-                    break;
-                case WINDOW_STATE_HIDING:
-                case WINDOW_STATE_HIDDEN:
-                    mView.setVisibility(View.VISIBLE);
-                    break;
-            }
-        });
+        mMainExecutor.execute(this::updateVisibility);
     }
 
     private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index fd6cfc0..100ccc3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -28,6 +28,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.lifecycle.LifecycleOwner;
 
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.util.ViewController;
 
 import java.util.Collection;
@@ -49,20 +50,34 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final ComplicationLayoutEngine mLayoutEngine;
+    private final DreamOverlayStateController mDreamOverlayStateController;
     private final LifecycleOwner mLifecycleOwner;
     private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
     private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
 
+    // Whether dream entry animations are finished.
+    private boolean mEntryAnimationsFinished = false;
+
     @Inject
     protected ComplicationHostViewController(
             @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
             ComplicationLayoutEngine layoutEngine,
+            DreamOverlayStateController dreamOverlayStateController,
             LifecycleOwner lifecycleOwner,
             @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
         super(view);
         mLayoutEngine = layoutEngine;
         mLifecycleOwner = lifecycleOwner;
         mComplicationCollectionViewModel = viewModel;
+        mDreamOverlayStateController = dreamOverlayStateController;
+
+        mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+            @Override
+            public void onStateChanged() {
+                mEntryAnimationsFinished =
+                        mDreamOverlayStateController.areEntryAnimationsFinished();
+            }
+        });
     }
 
     @Override
@@ -123,6 +138,11 @@
                     final ComplicationId id = complication.getId();
                     final Complication.ViewHolder viewHolder = complication.getComplication()
                             .createView(complication);
+                    // Complications to be added before dream entry animations are finished are set
+                    // to invisible and are animated in.
+                    if (!mEntryAnimationsFinished) {
+                        viewHolder.getView().setVisibility(View.INVISIBLE);
+                    }
                     mComplications.put(id, viewHolder);
                     if (viewHolder.getView().getParent() != null) {
                         Log.e(TAG, "View for complication "
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4fe1622..cb012fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -47,6 +47,14 @@
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
             "burn_in_protection_update_interval";
     public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+    public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
+    public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
+    public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
+            "dream_in_complications_anim_duration";
+    public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
+            "dream_in_top_complications_anim_delay";
+    public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
+            "dream_in_bottom_complications_anim_delay";
 
     /** */
     @Provides
@@ -114,6 +122,51 @@
         return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter);
     }
 
+    /**
+     * Duration in milliseconds of the dream in un-blur animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
+    static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in un-blur animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
+    static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+    }
+
+    /**
+     * Duration in milliseconds of the dream in complications fade-in animation.
+     */
+    @Provides
+    @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+    static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in top complications fade-in animation.
+     */
+    @Provides
+    @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+    static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in bottom complications fade-in animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+    static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+    }
+
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
     static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 0dba4ff..92cdcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -116,7 +116,7 @@
 
                         if (mCapture) {
                             // Since the user is dragging the bouncer up, set scrimmed to false.
-                            mStatusBarKeyguardViewManager.showBouncer(false);
+                            mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index fb4fc92..a49aaccf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -44,4 +44,10 @@
 
     /** Returns a string value for the given flag.  */
     fun getString(flag: ResourceStringFlag): String
+
+    /** Returns an int value for a given flag/ */
+    fun getInt(flag: IntFlag): Int
+
+    /** Returns an int value for a given flag/ */
+    fun getInt(flag: ResourceIntFlag): Int
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 1f061e9..910c87a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -82,6 +82,7 @@
     private final Map<Integer, Flag<?>> mAllFlags;
     private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
     private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
+    private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>();
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -210,6 +211,31 @@
         return mStringFlagCache.get(id);
     }
 
+    @NonNull
+    @Override
+    public int getInt(@NonNull IntFlag flag) {
+        int id = flag.getId();
+        if (!mIntFlagCache.containsKey(id)) {
+            mIntFlagCache.put(id,
+                    readFlagValue(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
+        }
+
+        return mIntFlagCache.get(id);
+    }
+
+    @NonNull
+    @Override
+    public int getInt(@NonNull ResourceIntFlag flag) {
+        int id = flag.getId();
+        if (!mIntFlagCache.containsKey(id)) {
+            mIntFlagCache.put(id,
+                    readFlagValue(id, mResources.getInteger(flag.getResourceId()),
+                            IntFlagSerializer.INSTANCE));
+        }
+
+        return mIntFlagCache.get(id);
+    }
+
     /** Specific override for Boolean flags that checks against the teamfood list. */
     private boolean readFlagValue(int id, boolean defaultValue) {
         Boolean result = readBooleanFlagOverride(id);
@@ -352,6 +378,16 @@
         }
     }
 
+    void setIntFlagInternal(Flag<?> flag, int value) {
+        if (flag instanceof IntFlag) {
+            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+        } else if (flag instanceof ResourceIntFlag) {
+            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+        } else {
+            throw new IllegalArgumentException("Unknown flag type");
+        }
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 30cad5f..8996d52 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -165,6 +165,18 @@
         return defaultValue;
     }
 
+    @NonNull
+    @Override
+    public int getInt(@NonNull IntFlag flag) {
+        return flag.getDefault();
+    }
+
+    @NonNull
+    @Override
+    public int getInt(@NonNull ResourceIntFlag flag) {
+        return mResources.getInteger(flag.getResourceId());
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 1e93c0b7..ad4b87d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -23,7 +23,6 @@
 import com.android.systemui.statusbar.commandline.Command;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Field;
 import java.util.List;
 import java.util.Map;
 
@@ -38,6 +37,7 @@
 
     private final List<String> mOnCommands = List.of("true", "on", "1", "enabled");
     private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
+    private final List<String> mSetCommands = List.of("set", "put");
     private final FeatureFlagsDebug mFeatureFlags;
     private final Map<Integer, Flag<?>> mAllFlags;
 
@@ -60,12 +60,6 @@
             return;
         }
 
-        if (args.size() > 2) {
-            pw.println("Invalid number of arguments.");
-            help(pw);
-            return;
-        }
-
         int id = 0;
         try {
             id = Integer.parseInt(args.get(0));
@@ -85,48 +79,113 @@
         Flag<?> flag = mAllFlags.get(id);
 
         String cmd = "";
-        if (args.size() == 2) {
+        if (args.size() > 1) {
             cmd = args.get(1).toLowerCase();
         }
 
         if ("erase".equals(cmd) || "reset".equals(cmd)) {
+            if (args.size() > 2) {
+                pw.println("Invalid number of arguments to reset a flag.");
+                help(pw);
+                return;
+            }
+
             mFeatureFlags.eraseFlag(flag);
             return;
         }
 
-        boolean newValue = true;
-        if (args.size() == 1 || "toggle".equals(cmd)) {
-            boolean enabled = isBooleanFlagEnabled(flag);
-
-            if (args.size() == 1) {
-                pw.println("Flag " + id + " is " + enabled);
+        boolean shouldSet = true;
+        if (args.size() == 1) {
+            shouldSet = false;
+        }
+        if (isBooleanFlag(flag)) {
+            if (args.size() > 2) {
+                pw.println("Invalid number of arguments for a boolean flag.");
+                help(pw);
                 return;
             }
-
-            newValue = !enabled;
-        } else {
-            newValue = mOnCommands.contains(cmd);
-            if (!newValue && !mOffCommands.contains(cmd)) {
+            boolean newValue = isBooleanFlagEnabled(flag);
+            if ("toggle".equals(cmd)) {
+                newValue = !newValue;
+            } else if (mOnCommands.contains(cmd)) {
+                newValue = true;
+            } else if (mOffCommands.contains(cmd)) {
+                newValue = false;
+            } else if (shouldSet) {
                 pw.println("Invalid on/off argument supplied");
                 help(pw);
                 return;
             }
-        }
 
-        pw.flush();  // Next command will restart sysui, so flush before we do so.
-        mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+            pw.println("Flag " + id + " is " + newValue);
+            pw.flush();  // Next command will restart sysui, so flush before we do so.
+            if (shouldSet) {
+                mFeatureFlags.setBooleanFlagInternal(flag, newValue);
+            }
+            return;
+
+        } else if (isStringFlag(flag)) {
+            if (shouldSet) {
+                if (args.size() != 3) {
+                    pw.println("Invalid number of arguments a StringFlag.");
+                    help(pw);
+                    return;
+                } else if (!mSetCommands.contains(cmd)) {
+                    pw.println("Unknown command: " + cmd);
+                    help(pw);
+                    return;
+                }
+                String value = args.get(2);
+                pw.println("Setting Flag " + id + " to " + value);
+                pw.flush();  // Next command will restart sysui, so flush before we do so.
+                mFeatureFlags.setStringFlagInternal(flag, args.get(2));
+            } else {
+                pw.println("Flag " + id + " is " + getStringFlag(flag));
+            }
+            return;
+        } else if (isIntFlag(flag)) {
+            if (shouldSet) {
+                if (args.size() != 3) {
+                    pw.println("Invalid number of arguments for an IntFlag.");
+                    help(pw);
+                    return;
+                } else if (!mSetCommands.contains(cmd)) {
+                    pw.println("Unknown command: " + cmd);
+                    help(pw);
+                    return;
+                }
+                int value = Integer.parseInt(args.get(2));
+                pw.println("Setting Flag " + id + " to " + value);
+                pw.flush();  // Next command will restart sysui, so flush before we do so.
+                mFeatureFlags.setIntFlagInternal(flag, value);
+            } else {
+                pw.println("Flag " + id + " is " + getIntFlag(flag));
+            }
+            return;
+        }
     }
 
     @Override
     public void help(PrintWriter pw) {
-        pw.println(
-                "Usage: adb shell cmd statusbar flag <id> "
+        pw.println("Usage: adb shell cmd statusbar flag <id> [options]");
+        pw.println();
+        pw.println("  Boolean Flag Options: "
                         + "[true|false|1|0|on|off|enable|disable|toggle|erase|reset]");
+        pw.println("  String Flag Options: [set|put \"<value>\"]");
+        pw.println("  Int Flag Options: [set|put <value>]");
+        pw.println();
         pw.println("The id can either be a numeric integer or the corresponding field name");
         pw.println(
                 "If no argument is supplied after the id, the flags runtime value is output");
     }
 
+    private boolean isBooleanFlag(Flag<?> flag) {
+        return (flag instanceof BooleanFlag)
+                || (flag instanceof ResourceBooleanFlag)
+                || (flag instanceof SysPropFlag)
+                || (flag instanceof DeviceConfigBooleanFlag);
+    }
+
     private boolean isBooleanFlagEnabled(Flag<?> flag) {
         if (flag instanceof ReleasedFlag) {
             return mFeatureFlags.isEnabled((ReleasedFlag) flag);
@@ -141,34 +200,51 @@
         return false;
     }
 
+    private boolean isStringFlag(Flag<?> flag) {
+        return (flag instanceof StringFlag) || (flag instanceof ResourceStringFlag);
+    }
+
+    private String getStringFlag(Flag<?> flag) {
+        if (flag instanceof StringFlag) {
+            return mFeatureFlags.getString((StringFlag) flag);
+        } else if (flag instanceof ResourceStringFlag) {
+            return mFeatureFlags.getString((ResourceStringFlag) flag);
+        }
+
+        return "";
+    }
+
+    private boolean isIntFlag(Flag<?> flag) {
+        return (flag instanceof IntFlag) || (flag instanceof ResourceIntFlag);
+    }
+
+    private int getIntFlag(Flag<?> flag) {
+        if (flag instanceof IntFlag) {
+            return mFeatureFlags.getInt((IntFlag) flag);
+        } else if (flag instanceof ResourceIntFlag) {
+            return mFeatureFlags.getInt((ResourceIntFlag) flag);
+        }
+
+        return 0;
+    }
+
     private int flagNameToId(String flagName) {
-        List<Field> fields = Flags.getFlagFields();
-        for (Field field : fields) {
-            if (flagName.equals(field.getName())) {
-                return fieldToId(field);
+        Map<String, Flag<?>> flagFields = Flags.getFlagFields();
+        for (String fieldName : flagFields.keySet()) {
+            if (flagName.equals(fieldName)) {
+                return flagFields.get(fieldName).getId();
             }
         }
 
         return 0;
     }
 
-    private int fieldToId(Field field) {
-        try {
-            Flag<?> flag = (Flag<?>) field.get(null);
-            return flag.getId();
-        } catch (IllegalAccessException e) {
-            // no-op
-        }
-
-        return 0;
-    }
-
     private void printKnownFlags(PrintWriter pw) {
-        List<Field> fields = Flags.getFlagFields();
+        Map<String, Flag<?>> fields = Flags.getFlagFields();
 
         int longestFieldName = 0;
-        for (Field field : fields) {
-            longestFieldName = Math.max(longestFieldName, field.getName().length());
+        for (String fieldName : fields.keySet()) {
+            longestFieldName = Math.max(longestFieldName, fieldName.length());
         }
 
         pw.println("Known Flags:");
@@ -176,23 +252,32 @@
         for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
             pw.print(" ");
         }
-        pw.println("ID   Enabled?");
+        pw.println("ID   Value");
         for (int i = 0; i < longestFieldName; i++) {
             pw.print("=");
         }
         pw.println(" ==== ========");
-        for (Field field : fields) {
-            int id = fieldToId(field);
+        for (String fieldName : fields.keySet()) {
+            Flag<?> flag = fields.get(fieldName);
+            int id = flag.getId();
             if (id == 0 || !mAllFlags.containsKey(id)) {
                 continue;
             }
-            pw.print(field.getName());
-            int fieldWidth = field.getName().length();
+            pw.print(fieldName);
+            int fieldWidth = fieldName.length();
             for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
                 pw.print(" ");
             }
             pw.printf("%-4d ", id);
-            pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+            if (isBooleanFlag(flag)) {
+                pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+            } else if (isStringFlag(flag)) {
+                pw.println(getStringFlag(flag));
+            } else if (isIntFlag(flag)) {
+                pw.println(getIntFlag(flag));
+            } else {
+                pw.println("<unknown flag type>");
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 2a94e52..931d9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -17,8 +17,12 @@
 
 import android.provider.DeviceConfig
 import com.android.internal.annotations.Keep
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
-import java.lang.reflect.Field
+import kotlin.reflect.KClass
+import kotlin.reflect.full.declaredMembers
+import kotlin.reflect.full.isSubclassOf
+import kotlin.reflect.full.staticProperties
 
 /**
  * List of [Flag] objects for use in SystemUI.
@@ -57,12 +61,13 @@
     val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
 
     // TODO(b/254512425): Tracking Bug
-    val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
+    val NOTIFICATION_MEMORY_MONITOR_ENABLED = ReleasedFlag(112)
 
     // TODO(b/254512731): Tracking Bug
     @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
     val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
     val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
+
     @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, teamfood = true)
     // next id: 117
 
@@ -89,7 +94,7 @@
      * replacement of KeyguardBouncer.java.
      */
     // TODO(b/254512385): Tracking Bug
-    @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
+    @JvmField val MODERN_BOUNCER = ReleasedFlag(208)
 
     /**
      * Whether the user interactor and repository should use `UserSwitcherController`.
@@ -129,6 +134,14 @@
 
     @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
 
+    /**
+     * Whether to enable the code powering customizable lock screen quick affordances.
+     *
+     * Note that this flag does not enable individual implementations of quick affordances like the
+     * new camera quick affordance. Look for individual flags for those.
+     */
+    @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(216, teamfood = false)
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -140,20 +153,17 @@
     val SMARTSPACE = ResourceBooleanFlag(402, R.bool.flag_smartspace)
 
     // 500 - quick settings
-    @Deprecated("Not needed anymore") val NEW_USER_SWITCHER = ReleasedFlag(500)
 
     // TODO(b/254512321): Tracking Bug
-    @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
+    @JvmField val COMBINED_QS_HEADERS = ReleasedFlag(501)
     val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
+
     @JvmField
     val QS_USER_DETAIL_SHORTCUT =
         ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut)
 
-    // TODO(b/254512699): Tracking Bug
-    @Deprecated("Not needed anymore") val NEW_FOOTER = ReleasedFlag(504)
-
     // TODO(b/254512747): Tracking Bug
-    val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
+    val NEW_HEADER = ReleasedFlag(505)
 
     // TODO(b/254512383): Tracking Bug
     @JvmField
@@ -178,10 +188,18 @@
     @Deprecated("Replaced by mobile and wifi specific flags.")
     val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
 
+    // TODO(b/256614753): Tracking Bug
     val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
 
+    // TODO(b/256614210): Tracking Bug
     val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
 
+    // TODO(b/256614751): Tracking Bug
+    val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608)
+
+    // TODO(b/256613548): Tracking Bug
+    val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609)
+
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
     val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
@@ -208,7 +226,8 @@
     val SCREEN_CONTENTS_TRANSLATION = UnreleasedFlag(803)
 
     // 804 - monochromatic themes
-    @JvmField val MONOCHROMATIC_THEMES = UnreleasedFlag(804)
+    @JvmField
+    val MONOCHROMATIC_THEMES = SysPropBooleanFlag(804, "persist.sysui.monochromatic", false)
 
     // 900 - media
     // TODO(b/254512697): Tracking Bug
@@ -239,20 +258,14 @@
     @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
 
     // 1100 - windowing
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_SHELL_TRANSITIONS =
         SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
 
-    /** b/170163464: animate bubbles expanded view collapse with home gesture */
-    @JvmField
-    @Keep
-    val BUBBLES_HOME_GESTURE =
-        SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
-
     // TODO(b/254513207): Tracking Bug
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING =
         DeviceConfigBooleanFlag(
             1102,
@@ -263,55 +276,49 @@
         )
 
     // TODO(b/254512674): Tracking Bug
-    @JvmField
     @Keep
+    @JvmField
     val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
 
-    @JvmField
     @Keep
-    val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
-
     @JvmField
-    @Keep
-    val SHOW_FLOATING_TASKS_AS_BUBBLES =
-        SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
-
-    @JvmField
-    @Keep
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
         SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
 
-    @JvmField
     @Keep
+    @JvmField
     val ENABLE_FLING_TO_DISMISS_PIP =
         SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
 
-    @JvmField
     @Keep
+    @JvmField
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
         SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
 
+    // TODO(b/256873975): Tracking Bug
+    @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111)
+
     // 1200 - predictive back
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
         SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
 
-    @JvmField
     @Keep
+    @JvmField
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
 
@@ -320,7 +327,7 @@
 
     // 1300 - screenshots
     // TODO(b/254512719): Tracking Bug
-    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true)
+    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, teamfood = true)
 
     // TODO(b/254513155): Tracking Bug
     @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
@@ -337,7 +344,7 @@
     @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, teamfood = true)
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
 
     // 1800 - shade container
@@ -346,30 +353,60 @@
     // 1900 - note task
     @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
 
+    // 2000 - device controls
+    @Keep @JvmField val USE_APP_PANELS = UnreleasedFlag(2000, teamfood = true)
+
+    // 2100 - Falsing Manager
+    @JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100)
+
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
     // |                                                           |
     // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
     @JvmStatic
     fun collectFlags(): Map<Int, Flag<*>> {
-        return flagFields
-            .map { field ->
-                // field[null] returns the current value of the field.
-                // See java.lang.Field#get
-                val flag = field[null] as Flag<*>
-                flag.id to flag
-            }
-            .toMap()
+        return flagFields.mapKeys { field -> field.value.id }
     }
 
     // |  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  |
     @JvmStatic
-    val flagFields: List<Field>
-        get() {
-            return Flags::class.java.fields.filter { f ->
-                Flag::class.java.isAssignableFrom(f.type)
+    val flagFields: Map<String, Flag<*>>
+        get() = collectFlagsInClass(Flags)
+
+    @VisibleForTesting
+    fun collectFlagsInClass(instance: Any): Map<String, Flag<*>> {
+        val cls = instance::class
+        val javaPropNames = cls.java.fields.map { it.name }
+        val props = cls.declaredMembers
+        val staticProps = cls.staticProperties
+        val staticPropNames = staticProps.map { it.name }
+        return props
+            .mapNotNull { property ->
+                if ((property.returnType.classifier as KClass<*>).isSubclassOf(Flag::class)) {
+                    // Fields with @JvmStatic should be accessed via java mechanisms
+                    if (javaPropNames.contains(property.name)) {
+                        property.name to cls.java.getField(property.name)[null] as Flag<*>
+                        // Fields with @Keep but not @JvmField. Don't do this.
+                    } else if (staticPropNames.contains(property.name)) {
+                        // The below code causes access violation exceptions. I don't know why.
+                        // property.name to (property.call() as Flag<*>)
+                        // property.name to (staticProps.find { it.name == property.name }!!
+                        // .getter.call() as Flag<*>)
+                        throw java.lang.RuntimeException(
+                            "The {$property.name} flag needs @JvmField"
+                        )
+                        // Everything else. Skip the `get` prefixed fields that kotlin adds.
+                    } else if (property.name.subSequence(0, 3) != "get") {
+                        property.name to (property.call(instance) as Flag<*>)
+                    } else {
+                        null
+                    }
+                } else {
+                    null
+                }
             }
-        }
+            .toMap()
+    }
     // |                                                           |
     // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/OWNERS b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
new file mode 100644
index 0000000..c9d2db1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+# Bug component: 1203176
+
+mankoff@google.com # send reviews here
+
+pixel@google.com
+juliacr@google.com
+cinek@google.com
+alexflo@google.com
+dsandler@android.com
+adamcohen@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 41abb62..04a74ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -125,6 +125,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
@@ -266,6 +267,7 @@
     private final Executor mUiBgExecutor;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
+    private final Lazy<ShadeController> mShadeController;
 
     private boolean mSystemReady;
     private boolean mBootCompleted;
@@ -1150,6 +1152,7 @@
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
         mContext = context;
@@ -1165,6 +1168,7 @@
         mTrustManager = trustManager;
         mUserSwitcherController = userSwitcherController;
         mKeyguardDisplayManager = keyguardDisplayManager;
+        mShadeController = shadeControllerLazy;
         dumpManager.registerDumpable(getClass().getName(), this);
         mDeviceConfig = deviceConfig;
         mScreenOnCoordinator = screenOnCoordinator;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 546a409..450fa14 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -33,6 +33,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.widget.ImageView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
@@ -61,6 +63,7 @@
     private UserManager mUserManager;
     private PackageManager mPackageManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     @Inject
     public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager,
@@ -95,6 +98,10 @@
         if (badgedIcon != null) {
             ((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon);
         }
+
+        getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                mBackCallback);
     }
 
     @VisibleForTesting
@@ -134,11 +141,16 @@
     @Override
     public void onDestroy() {
         unregisterBroadcastReceiver();
+        getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
         super.onDestroy();
     }
 
     @Override
     public void onBackPressed() {
+        onBackInvoked();
+    }
+
+    private void onBackInvoked() {
         // Ignore back presses.
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56a1f1a..78a7c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,10 +42,12 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
 import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -71,6 +73,7 @@
         KeyguardUserSwitcherComponent.class},
         includes = {
             FalsingModule.class,
+            KeyguardDataQuickAffordanceModule.class,
             KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
             StartKeyguardTransitionModule.class,
@@ -106,6 +109,7 @@
             ScreenOnCoordinator screenOnCoordinator,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
         return new KeyguardViewMediator(
@@ -133,6 +137,7 @@
                 screenOnCoordinator,
                 interactionJankMonitor,
                 dreamOverlayStateController,
+                shadeController,
                 notificationShadeWindowController,
                 activityLaunchAnimator);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index c600e13..d6f521c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -53,6 +53,10 @@
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
 
+    override val pickerName: String by lazy { context.getString(component.getTileTitleId()) }
+
+    override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
+
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
             if (canShowWhileLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
new file mode 100644
index 0000000..bea9363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+
+@Module
+object KeyguardDataQuickAffordanceModule {
+    @Provides
+    @ElementsIntoSet
+    fun quickAffordanceConfigs(
+        home: HomeControlsKeyguardQuickAffordanceConfig,
+        quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+        qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+    ): Set<KeyguardQuickAffordanceConfig> {
+        return setOf(
+            home,
+            quickAccessWallet,
+            qrCodeScanner,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 0a8090b..fd40d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -29,6 +29,10 @@
     /** Unique identifier for this quick affordance. It must be globally unique. */
     val key: String
 
+    val pickerName: String
+
+    val pickerIconResourceId: Int
+
     /**
      * The ever-changing state of the affordance.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
new file mode 100644
index 0000000..9c9354f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?".
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+
+    // TODO(b/254858695): implement a persistence layer (database).
+    private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+    /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
+    val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+
+    /**
+     * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
+     * descending priority order.
+     */
+    suspend fun getSelections(): Map<String, List<String>> {
+        return _selections.value
+    }
+
+    /**
+     * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+     * IDs should be descending priority order.
+     */
+    suspend fun setSelections(
+        slotId: String,
+        affordanceIds: List<String>,
+    ) {
+        // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
+        // when we set its value to the same instance of the original map, even if we change the
+        // map by updating the value of one of its keys.
+        val copy = _selections.value.toMutableMap()
+        copy[slotId] = affordanceIds
+        _selections.value = copy
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d620b2a..11f72ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.Context
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -24,6 +25,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -34,11 +36,16 @@
 class QrCodeScannerKeyguardQuickAffordanceConfig
 @Inject
 constructor(
+    @Application context: Context,
     private val controller: QRCodeScannerController,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
 
+    override val pickerName = context.getString(R.string.qr_code_scanner_title)
+
+    override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
+
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         conflatedCallbackFlow {
             val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index be57a32..303e6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.Context
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsError
 import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -29,6 +30,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import javax.inject.Inject
@@ -40,12 +42,17 @@
 class QuickAccessWalletKeyguardQuickAffordanceConfig
 @Inject
 constructor(
+    @Application context: Context,
     private val walletController: QuickAccessWalletController,
     private val activityStarter: ActivityStarter,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
 
+    override val pickerName = context.getString(R.string.accessibility_wallet_button)
+
+    override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
+
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         conflatedCallbackFlow {
             val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 0046256..d4514c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -28,7 +28,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-/** Encapsulates app state for the lock screen bouncer. */
+/** Encapsulates app state for the lock screen primary and alternate bouncer. */
 @SysUISingleton
 class KeyguardBouncerRepository
 @Inject
@@ -36,12 +36,24 @@
     private val viewMediatorCallback: ViewMediatorCallback,
     keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) {
-    var bouncerPromptReason: Int? = null
-    /** Determines if we want to instantaneously show the bouncer instead of translating. */
-    private val _isScrimmed = MutableStateFlow(false)
-    val isScrimmed = _isScrimmed.asStateFlow()
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    private val _primaryBouncerVisible = MutableStateFlow(false)
+    val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+    val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerHide = MutableStateFlow(false)
+    val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
     /**
-     * Set how much of the panel is showing on the screen.
+     * Set how much of the notification panel is showing on the screen.
      * ```
      *      0f = panel fully hidden = bouncer fully showing
      *      1f = panel fully showing = bouncer fully hidden
@@ -49,31 +61,21 @@
      */
     private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
     val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _isVisible = MutableStateFlow(false)
-    val isVisible = _isVisible.asStateFlow()
-    private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
-    val show = _show.asStateFlow()
-    private val _showingSoon = MutableStateFlow(false)
-    val showingSoon = _showingSoon.asStateFlow()
-    private val _hide = MutableStateFlow(false)
-    val hide = _hide.asStateFlow()
-    private val _startingToHide = MutableStateFlow(false)
-    val startingToHide = _startingToHide.asStateFlow()
-    private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
-    val startingDisappearAnimation = _disappearAnimation.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
     val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _resourceUpdateRequests = MutableStateFlow(false)
-    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    val showMessage = _showMessage.asStateFlow()
+    private val _onScreenTurnedOff = MutableStateFlow(false)
+    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
     /** Determines if user is already unlocked */
     val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _onScreenTurnedOff = MutableStateFlow(false)
-    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+
+    var bouncerPromptReason: Int? = null
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
 
     val bouncerErrorMessage: CharSequence?
         get() = viewMediatorCallback.consumeCustomMessage()
@@ -95,38 +97,38 @@
         keyguardUpdateMonitor.registerCallback(callback)
     }
 
-    fun setScrimmed(isScrimmed: Boolean) {
-        _isScrimmed.value = isScrimmed
+    fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    fun setPrimaryVisible(isVisible: Boolean) {
+        _primaryBouncerVisible.value = isVisible
+    }
+
+    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+        _primaryBouncerShow.value = keyguardBouncerModel
+    }
+
+    fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    fun setPrimaryHide(hide: Boolean) {
+        _primaryBouncerHide.value = hide
+    }
+
+    fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
     }
 
     fun setPanelExpansion(panelExpansion: Float) {
         _panelExpansionAmount.value = panelExpansion
     }
 
-    fun setVisible(isVisible: Boolean) {
-        _isVisible.value = isVisible
-    }
-
-    fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) {
-        _show.value = keyguardBouncerModel
-    }
-
-    fun setShowingSoon(showingSoon: Boolean) {
-        _showingSoon.value = showingSoon
-    }
-
-    fun setHide(hide: Boolean) {
-        _hide.value = hide
-    }
-
-    fun setStartingToHide(startingToHide: Boolean) {
-        _startingToHide.value = startingToHide
-    }
-
-    fun setStartDisappearAnimation(runnable: Runnable?) {
-        _disappearAnimation.value = runnable
-    }
-
     fun setKeyguardPosition(keyguardPosition: Float) {
         _keyguardPosition.value = keyguardPosition
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..95f614f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Abstracts access to application state related to keyguard quick affordances. */
+@SysUISingleton
+class KeyguardQuickAffordanceRepository
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+    private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+) {
+    /**
+     * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
+     * given ID. The configs are sorted in descending priority order.
+     */
+    val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
+        selectionManager.selections
+            .map { selectionsBySlotId ->
+                selectionsBySlotId.mapValues { (_, selections) ->
+                    configs.filter { selections.contains(it.key) }
+                }
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.Eagerly,
+                initialValue = emptyMap(),
+            )
+
+    /**
+     * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
+     * slot with the given ID. The configs are sorted in descending priority order.
+     */
+    suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+        val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+        return configs.filter { selections.contains(it.key) }
+    }
+
+    /**
+     * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
+     * are sorted in descending priority order.
+     */
+    suspend fun getSelections(): Map<String, List<String>> {
+        return selectionManager.getSelections()
+    }
+
+    /**
+     * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+     * IDs should be descending priority order.
+     */
+    fun setSelections(
+        slotId: String,
+        affordanceIds: List<String>,
+    ) {
+        scope.launch(backgroundDispatcher) {
+            selectionManager.setSelections(
+                slotId = slotId,
+                affordanceIds = affordanceIds,
+            )
+        }
+    }
+
+    /**
+     * Returns the list of representation objects for all known affordances, regardless of what is
+     * selected. This is useful for building experiences like the picker/selector or user settings
+     * so the user can see everything that can be selected in a menu.
+     */
+    fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+        return configs.map { config ->
+            KeyguardQuickAffordancePickerRepresentation(
+                id = config.key,
+                name = config.pickerName,
+                iconResourceId = config.pickerIconResourceId,
+            )
+        }
+    }
+
+    /**
+     * Returns the list of representation objects for all available slots on the keyguard. This is
+     * useful for building experiences like the picker/selector or user settings so the user can see
+     * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
+     */
+    fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+        // TODO(b/256195304): source these from a config XML file.
+        return listOf(
+            KeyguardSlotPickerRepresentation(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+            ),
+            KeyguardSlotPickerRepresentation(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+            ),
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 6baaf5f..ca25282 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
@@ -23,9 +24,12 @@
 import com.android.systemui.doze.DozeHost
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -65,6 +69,9 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Observable for whether the bouncer is showing. */
+    val isBouncerShowing: Flow<Boolean>
+
     /**
      * Observable for whether we are in doze state.
      *
@@ -95,6 +102,9 @@
     /** Observable for device wake/sleep state */
     val wakefulnessState: Flow<WakefulnessModel>
 
+    /** Observable for biometric unlock modes */
+    val biometricUnlockState: Flow<BiometricUnlockModel>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -114,6 +124,11 @@
      * Sets the relative offset of the lock-screen clock from its natural position on the screen.
      */
     fun setClockPosition(x: Int, y: Int)
+
+    /**
+     * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+     */
+    fun isUdfpsSupported(): Boolean
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -121,10 +136,12 @@
 class KeyguardRepositoryImpl
 @Inject
 constructor(
-    statusBarStateController: StatusBarStateController,
-    private val keyguardStateController: KeyguardStateController,
-    dozeHost: DozeHost,
-    wakefulnessLifecycle: WakefulnessLifecycle,
+        statusBarStateController: StatusBarStateController,
+        dozeHost: DozeHost,
+        wakefulnessLifecycle: WakefulnessLifecycle,
+        biometricUnlockController: BiometricUnlockController,
+        private val keyguardStateController: KeyguardStateController,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -159,6 +176,29 @@
         awaitClose { keyguardStateController.removeCallback(callback) }
     }
 
+    override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onBouncerShowingChanged() {
+                    trySendWithFailureLogging(
+                        keyguardStateController.isBouncerShowing,
+                        TAG,
+                        "updated isBouncerShowing"
+                    )
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+        // Adding the callback does not send an initial update.
+        trySendWithFailureLogging(
+            keyguardStateController.isBouncerShowing,
+            TAG,
+            "initial isBouncerShowing"
+        )
+
+        awaitClose { keyguardStateController.removeCallback(callback) }
+    }
+
     override val isDozing: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
@@ -248,6 +288,24 @@
         awaitClose { wakefulnessLifecycle.removeObserver(callback) }
     }
 
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+        val callback =
+            object : BiometricUnlockController.BiometricModeListener {
+                override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+                    trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+                }
+            }
+
+        biometricUnlockController.addBiometricModeListener(callback)
+        trySendWithFailureLogging(
+            biometricModeIntToObject(biometricUnlockController.getMode()),
+            TAG,
+            "initial biometric mode"
+        )
+
+        awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -260,6 +318,8 @@
         _clockPosition.value = Position(x, y)
     }
 
+    override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
@@ -279,6 +339,20 @@
         }
     }
 
+    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+        return when (value) {
+            0 -> BiometricUnlockModel.NONE
+            1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+            2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            3 -> BiometricUnlockModel.SHOW_BOUNCER
+            4 -> BiometricUnlockModel.ONLY_WAKE
+            5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+            6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            7 -> BiometricUnlockModel.DISMISS_BOUNCER
+            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+        }
+    }
+
     companion object {
         private const val TAG = "KeyguardRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..7e01db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+    private val wakeAndUnlockModes =
+        setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (biometricUnlockState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.AOD &&
+                            wakeAndUnlockModes.contains(biometricUnlockState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.AOD,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index ede50b0..d2a7486 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -48,4 +48,9 @@
     fun setAnimateDozingTransitions(animate: Boolean) {
         repository.setAnimateDozingTransitions(animate)
     }
+
+    /**
+     * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+     */
+    fun shouldConstrainToTopOfLockIcon(): Boolean = repository.isUdfpsSupported()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 03c6a78..614ff8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -39,10 +41,19 @@
     val dozeAmount: Flow<Float> = repository.dozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
-    /** Whether the keyguard is showing to not. */
+    /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the bouncer is showing or not. */
+    val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
     /** The device wake/sleep state */
     val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+    /** Observable for the [StatusBarState] */
+    val statusBarState: Flow<StatusBarState> = repository.statusBarState
+    /**
+     * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+     * side, under display) is used to unlock the device.
+     */
+    val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
 
     fun isKeyguardShowing(): Boolean {
         return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 13d97aa..92caa89 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
@@ -18,19 +18,30 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Intent
+import android.util.Log
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
 @SysUISingleton
@@ -43,7 +54,12 @@
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
     private val activityStarter: ActivityStarter,
+    private val featureFlags: FeatureFlags,
+    private val repository: Lazy<KeyguardQuickAffordanceRepository>,
 ) {
+    private val isUsingRepository: Boolean
+        get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
     /** Returns an observable for the quick affordance at the given position. */
     fun quickAffordance(
         position: KeyguardQuickAffordancePosition
@@ -72,7 +88,19 @@
         configKey: String,
         expandable: Expandable?,
     ) {
-        @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+        @Suppress("UNCHECKED_CAST")
+        val config =
+            if (isUsingRepository) {
+                val (slotId, decodedConfigKey) = configKey.decode()
+                repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
+            } else {
+                registry.get(configKey)
+            }
+        if (config == null) {
+            Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
+            return
+        }
+
         when (val result = config.onTriggered(expandable)) {
             is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
                 launchQuickAffordance(
@@ -84,28 +112,138 @@
         }
     }
 
+    /**
+     * Selects an affordance with the given ID on the slot with the given ID.
+     *
+     * @return `true` if the affordance was selected successfully; `false` otherwise.
+     */
+    suspend fun select(slotId: String, affordanceId: String): Boolean {
+        check(isUsingRepository)
+
+        val slots = repository.get().getSlotPickerRepresentations()
+        val slot = slots.find { it.id == slotId } ?: return false
+        val selections =
+            repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+        val alreadySelected = selections.remove(affordanceId)
+        if (!alreadySelected) {
+            while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
+                selections.removeAt(0)
+            }
+        }
+
+        selections.add(affordanceId)
+
+        repository
+            .get()
+            .setSelections(
+                slotId = slotId,
+                affordanceIds = selections,
+            )
+
+        return true
+    }
+
+    /**
+     * Unselects one or all affordances from the slot with the given ID.
+     *
+     * @param slotId The ID of the slot.
+     * @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances
+     * from the slot.
+     * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
+     * the affordance was not on the slot to begin with).
+     */
+    suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+        check(isUsingRepository)
+
+        val slots = repository.get().getSlotPickerRepresentations()
+        if (slots.find { it.id == slotId } == null) {
+            return false
+        }
+
+        if (affordanceId.isNullOrEmpty()) {
+            return if (
+                repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+            ) {
+                false
+            } else {
+                repository.get().setSelections(slotId = slotId, affordanceIds = emptyList())
+                true
+            }
+        }
+
+        val selections =
+            repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+        return if (selections.remove(affordanceId)) {
+            repository
+                .get()
+                .setSelections(
+                    slotId = slotId,
+                    affordanceIds = selections,
+                )
+            true
+        } else {
+            false
+        }
+    }
+
+    /** Returns affordance IDs indexed by slot ID, for all known slots. */
+    suspend fun getSelections(): Map<String, List<String>> {
+        check(isUsingRepository)
+
+        val selections = repository.get().getSelections()
+        return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
+            slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+        }
+    }
+
     private fun quickAffordanceInternal(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceModel> {
-        val configs = registry.getAll(position)
+        return if (isUsingRepository) {
+            repository
+                .get()
+                .selections
+                .map { it[position.toSlotId()] ?: emptyList() }
+                .flatMapLatest { configs -> combinedConfigs(position, configs) }
+        } else {
+            combinedConfigs(position, registry.getAll(position))
+        }
+    }
+
+    private fun combinedConfigs(
+        position: KeyguardQuickAffordancePosition,
+        configs: List<KeyguardQuickAffordanceConfig>,
+    ): Flow<KeyguardQuickAffordanceModel> {
+        if (configs.isEmpty()) {
+            return flowOf(KeyguardQuickAffordanceModel.Hidden)
+        }
+
         return combine(
             configs.map { config ->
-                // We emit an initial "Hidden" value to make sure that there's always an initial
-                // value and avoid subtle bugs where the downstream isn't receiving any values
-                // because one config implementation is not emitting an initial value. For example,
-                // see b/244296596.
+                // We emit an initial "Hidden" value to make sure that there's always an
+                // initial value and avoid subtle bugs where the downstream isn't receiving
+                // any values because one config implementation is not emitting an initial
+                // value. For example, see b/244296596.
                 config.lockScreenState.onStart {
                     emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
                 }
             }
         ) { states ->
             val index =
-                states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
+                states.indexOfFirst { state ->
+                    state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
+                }
             if (index != -1) {
                 val visibleState =
                     states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+                val configKey = configs[index].key
                 KeyguardQuickAffordanceModel.Visible(
-                    configKey = configs[index].key,
+                    configKey =
+                        if (isUsingRepository) {
+                            configKey.encode(position.toSlotId())
+                        } else {
+                            configKey
+                        },
                     icon = visibleState.icon,
                     activationState = visibleState.activationState,
                 )
@@ -145,4 +283,39 @@
             )
         }
     }
+
+    private fun KeyguardQuickAffordancePosition.toSlotId(): String {
+        return when (this) {
+            KeyguardQuickAffordancePosition.BOTTOM_START ->
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            KeyguardQuickAffordancePosition.BOTTOM_END ->
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+        }
+    }
+
+    private fun String.encode(slotId: String): String {
+        return "$slotId$DELIMITER$this"
+    }
+
+    private fun String.decode(): Pair<String, String> {
+        val splitUp = this.split(DELIMITER)
+        return Pair(splitUp[0], splitUp[1])
+    }
+
+    fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+        check(isUsingRepository)
+
+        return repository.get().getAffordancePickerRepresentations()
+    }
+
+    fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+        check(isUsingRepository)
+
+        return repository.get().getSlotPickerRepresentations()
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordanceInteractor"
+        private const val DELIMITER = "::"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 83d9432..57fb4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,11 +41,13 @@
         }
 
         scope.launch {
-            interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) }
+            interactor.finishedKeyguardTransitionStep.collect {
+                logger.i("Finished transition", it)
+            }
         }
 
         scope.launch {
-            interactor.startedKeyguardState.collect { logger.i("Started transition to", it) }
+            interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index d5ea77b..a7c6d44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -41,6 +41,7 @@
                     is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
                     is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
                     is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index dffd097..749183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,8 +43,9 @@
     /** LOCKSCREEN->AOD transition information. */
     val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
 
-    /** GONE->AOD information. */
-    val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+    /** (any)->AOD transition information */
+    val anyStateToAodTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == KeyguardState.AOD }
 
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -57,15 +57,15 @@
             lockscreenToAodTransition,
         )
 
+    /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+    val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
     /* The last completed [KeyguardState] transition */
     val finishedKeyguardState: Flow<KeyguardState> =
-        repository.transitions
-            .filter { step -> step.transitionState == TransitionState.FINISHED }
-            .map { step -> step.to }
+        finishedKeyguardTransitionStep.map { step -> step.to }
 
-    /* The last started [KeyguardState] transition */
-    val startedKeyguardState: Flow<KeyguardState> =
-        repository.transitions
-            .filter { step -> step.transitionState == TransitionState.STARTED }
-            .map { step -> step.to }
+    /* The last [TransitionStep] with a [TransitionState] of STARTED */
+    val startedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 761f3fd..fd4814d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,14 +16,16 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
@@ -38,7 +40,7 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -47,12 +49,47 @@
     private var transitionId: UUID? = null
 
     override fun start() {
+        listenForDraggingUpToBouncer()
+        listenForBouncerHiding()
+    }
+
+    private fun listenForBouncerHiding() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isBouncerShowing, wakefulnessState) = pair
+                    if (!isBouncerShowing) {
+                        val to =
+                            if (
+                                wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+                                    wakefulnessState == WakefulnessModel.ASLEEP
+                            ) {
+                                KeyguardState.AOD
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = to,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+    private fun listenForDraggingUpToBouncer() {
         scope.launch {
             shadeRepository.shadeModel
                 .sample(
                     combine(
                         keyguardTransitionInteractor.finishedKeyguardState,
-                        keyguardRepository.statusBarState,
+                        keyguardInteractor.statusBarState,
                     ) { keyguardState, statusBarState ->
                         Pair(keyguardState, statusBarState)
                     },
@@ -100,4 +137,15 @@
                 }
         }
     }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 300L
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
index 10c7a37..c5e49c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
@@ -24,9 +24,9 @@
 
 /** Interactor to add and remove callbacks for the bouncer. */
 @SysUISingleton
-class BouncerCallbackInteractor @Inject constructor() {
+class PrimaryBouncerCallbackInteractor @Inject constructor() {
     private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
-    private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>()
+    private var expansionCallbacks = ArrayList<KeyguardBouncer.PrimaryBouncerExpansionCallback>()
     /** Add a KeyguardResetCallback. */
     fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
         resetCallbacks.addIfAbsent(callback)
@@ -38,7 +38,7 @@
     }
 
     /** Adds a callback to listen to bouncer expansion updates. */
-    fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+    fun addBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
         if (!expansionCallbacks.contains(callback)) {
             expansionCallbacks.add(callback)
         }
@@ -48,7 +48,7 @@
      * Removes a previously added callback. If the callback was never added, this method does
      * nothing.
      */
-    fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
+    fun removeBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
         expansionCallbacks.remove(callback)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index dbb0352..c22f4e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -44,24 +44,27 @@
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 
-/** Encapsulates business logic for interacting with the lock-screen bouncer. */
+/**
+ * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
+ * bouncer.
+ */
 @SysUISingleton
-class BouncerInteractor
+class PrimaryBouncerInteractor
 @Inject
 constructor(
     private val repository: KeyguardBouncerRepository,
-    private val bouncerView: BouncerView,
+    private val primaryBouncerView: BouncerView,
     @Main private val mainHandler: Handler,
     private val keyguardStateController: KeyguardStateController,
     private val keyguardSecurityModel: KeyguardSecurityModel,
-    private val callbackInteractor: BouncerCallbackInteractor,
+    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
     private val falsingCollector: FalsingCollector,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
     keyguardBypassController: KeyguardBypassController,
     keyguardUpdateMonitor: KeyguardUpdateMonitor,
 ) {
     /** Whether we want to wait for face auth. */
-    private val bouncerFaceDelay =
+    private val primaryBouncerFaceDelay =
         keyguardStateController.isFaceAuthEnabled &&
             !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
                 KeyguardUpdateMonitor.getCurrentUser()
@@ -70,38 +73,39 @@
             !keyguardUpdateMonitor.userNeedsStrongAuth() &&
             !keyguardBypassController.bypassEnabled
 
-    /** Runnable to show the bouncer. */
+    /** Runnable to show the primary bouncer. */
     val showRunnable = Runnable {
-        repository.setVisible(true)
-        repository.setShow(
+        repository.setPrimaryVisible(true)
+        repository.setPrimaryShow(
             KeyguardBouncerModel(
                 promptReason = repository.bouncerPromptReason ?: 0,
                 errorMessage = repository.bouncerErrorMessage,
                 expansionAmount = repository.panelExpansionAmount.value
             )
         )
-        repository.setShowingSoon(false)
+        repository.setPrimaryShowingSoon(false)
     }
 
     val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
     val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
-    val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull()
-    val hide: Flow<Unit> = repository.hide.filter { it }.map {}
-    val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
-    val isVisible: Flow<Boolean> = repository.isVisible
+    val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull()
+    val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {}
+    val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
+    val isVisible: Flow<Boolean> = repository.primaryBouncerVisible
     val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
     val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
     val startingDisappearAnimation: Flow<Runnable> =
-        repository.startingDisappearAnimation.filterNotNull()
+        repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
     val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
     val keyguardPosition: Flow<Float> = repository.keyguardPosition
     val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
     /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
-    val bouncerExpansion: Flow<Float> = //
-        combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
-            ->
-            if (isVisible) {
-                1f - expansionAmount
+    val bouncerExpansion: Flow<Float> =
+        combine(repository.panelExpansionAmount, repository.primaryBouncerVisible) {
+            panelExpansion,
+            primaryBouncerVisible ->
+            if (primaryBouncerVisible) {
+                1f - panelExpansion
             } else {
                 0f
             }
@@ -116,13 +120,14 @@
         repository.setShowMessage(null)
         repository.setOnScreenTurnedOff(false)
         repository.setKeyguardAuthenticated(null)
-        repository.setHide(false)
-        repository.setStartingToHide(false)
+        repository.setPrimaryHide(false)
+        repository.setPrimaryStartingToHide(false)
 
         val resumeBouncer =
-            (repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer()
+            (repository.primaryBouncerVisible.value ||
+                repository.primaryBouncerShowingSoon.value) && needsFullscreenBouncer()
 
-        if (!resumeBouncer && repository.show.value != null) {
+        if (!resumeBouncer && repository.primaryBouncerShow.value != null) {
             // If bouncer is visible, the bouncer is already showing.
             return
         }
@@ -134,29 +139,29 @@
         }
 
         Trace.beginSection("KeyguardBouncer#show")
-        repository.setScrimmed(isScrimmed)
+        repository.setPrimaryScrimmed(isScrimmed)
         if (isScrimmed) {
             setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
         }
 
         if (resumeBouncer) {
-            bouncerView.delegate?.resume()
+            primaryBouncerView.delegate?.resume()
             // Bouncer is showing the next security screen and we just need to prompt a resume.
             return
         }
-        if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+        if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
             // Keyguard is done.
             return
         }
 
-        repository.setShowingSoon(true)
-        if (bouncerFaceDelay) {
+        repository.setPrimaryShowingSoon(true)
+        if (primaryBouncerFaceDelay) {
             mainHandler.postDelayed(showRunnable, 1200L)
         } else {
             DejankUtils.postAfterTraversal(showRunnable)
         }
         keyguardStateController.notifyBouncerShowing(true)
-        callbackInteractor.dispatchStartingToShow()
+        primaryBouncerCallbackInteractor.dispatchStartingToShow()
         Trace.endSection()
     }
 
@@ -174,10 +179,10 @@
         falsingCollector.onBouncerHidden()
         keyguardStateController.notifyBouncerShowing(false /* showing */)
         cancelShowRunnable()
-        repository.setShowingSoon(false)
-        repository.setVisible(false)
-        repository.setHide(true)
-        repository.setShow(null)
+        repository.setPrimaryShowingSoon(false)
+        repository.setPrimaryVisible(false)
+        repository.setPrimaryHide(true)
+        repository.setPrimaryShow(null)
         Trace.endSection()
     }
 
@@ -191,7 +196,7 @@
     fun setPanelExpansion(expansion: Float) {
         val oldExpansion = repository.panelExpansionAmount.value
         val expansionChanged = oldExpansion != expansion
-        if (repository.startingDisappearAnimation.value == null) {
+        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
             repository.setPanelExpansion(expansion)
         }
 
@@ -200,25 +205,25 @@
                 oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
         ) {
             falsingCollector.onBouncerShown()
-            callbackInteractor.dispatchFullyShown()
+            primaryBouncerCallbackInteractor.dispatchFullyShown()
         } else if (
             expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
                 oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
         ) {
-            repository.setVisible(false)
-            repository.setShow(null)
+            repository.setPrimaryVisible(false)
+            repository.setPrimaryShow(null)
             falsingCollector.onBouncerHidden()
-            DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() }
-            callbackInteractor.dispatchFullyHidden()
+            DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
+            primaryBouncerCallbackInteractor.dispatchFullyHidden()
         } else if (
             expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
                 oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
         ) {
-            callbackInteractor.dispatchStartingToHide()
-            repository.setStartingToHide(true)
+            primaryBouncerCallbackInteractor.dispatchStartingToHide()
+            repository.setPrimaryStartingToHide(true)
         }
         if (expansionChanged) {
-            callbackInteractor.dispatchExpansionChanged(expansion)
+            primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
         }
     }
 
@@ -236,7 +241,7 @@
         onDismissAction: ActivityStarter.OnDismissAction?,
         cancelAction: Runnable?
     ) {
-        bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
+        primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
     }
 
     /** Update the resources of the views. */
@@ -266,7 +271,7 @@
 
     /** Notify that view visibility has changed. */
     fun notifyBouncerVisibilityHasChanged(visibility: Int) {
-        callbackInteractor.dispatchVisibilityChanged(visibility)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(visibility)
     }
 
     /** Notify that the resources have been updated */
@@ -283,38 +288,39 @@
     fun startDisappearAnimation(runnable: Runnable) {
         val finishRunnable = Runnable {
             runnable.run()
-            repository.setStartDisappearAnimation(null)
+            repository.setPrimaryStartDisappearAnimation(null)
         }
-        repository.setStartDisappearAnimation(finishRunnable)
+        repository.setPrimaryStartDisappearAnimation(finishRunnable)
     }
 
     /** Returns whether bouncer is fully showing. */
     fun isFullyShowing(): Boolean {
-        return (repository.showingSoon.value || repository.isVisible.value) &&
+        return (repository.primaryBouncerShowingSoon.value ||
+            repository.primaryBouncerVisible.value) &&
             repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
-            repository.startingDisappearAnimation.value == null
+            repository.primaryBouncerStartingDisappearAnimation.value == null
     }
 
     /** Returns whether bouncer is scrimmed. */
     fun isScrimmed(): Boolean {
-        return repository.isScrimmed.value
+        return repository.primaryBouncerScrimmed.value
     }
 
     /** If bouncer expansion is between 0f and 1f non-inclusive. */
     fun isInTransit(): Boolean {
-        return repository.showingSoon.value ||
+        return repository.primaryBouncerShowingSoon.value ||
             repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
                 repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
     }
 
     /** Return whether bouncer is animating away. */
     fun isAnimatingAway(): Boolean {
-        return repository.startingDisappearAnimation.value != null
+        return repository.primaryBouncerStartingDisappearAnimation.value != null
     }
 
     /** Return whether bouncer will dismiss with actions */
     fun willDismissWithAction(): Boolean {
-        return bouncerView.delegate?.willDismissWithActions() == true
+        return primaryBouncerView.delegate?.willDismissWithActions() == true
     }
 
     /** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 728bafa..37f33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -42,6 +42,8 @@
 
     @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
 
+    @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
     @Binds
     @IntoSet
     abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 0000000..db709b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+    /** Mode in which we don't need to wake up the device when we authenticate. */
+    NONE,
+    /**
+     * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+     * fingerprint while the screen is off and the device was sleeping.
+     */
+    WAKE_AND_UNLOCK,
+    /**
+     * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+     * already visible while pulsing in doze mode.
+     */
+    WAKE_AND_UNLOCK_PULSING,
+    /**
+     * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+     * acquire a fingerprint pulsing in doze mode.
+     */
+    SHOW_BOUNCER,
+    /**
+     * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+     */
+    ONLY_WAKE,
+    /**
+     * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+     * device while being requested when keyguard is occluded or showing.
+     */
+    UNLOCK_COLLAPSING,
+    /** When bouncer is visible and will be dismissed. */
+    DISMISS_BOUNCER,
+    /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+    WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
similarity index 64%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index f79ca10..a56bc90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -12,17 +12,19 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.wm.shell.floating;
+package com.android.systemui.keyguard.shared.model
 
-import android.content.Intent;
+import androidx.annotation.DrawableRes
 
 /**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Representation of a quick affordance for use to build "picker", "selector", or "settings"
+ * experiences.
  */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
-}
+data class KeyguardQuickAffordancePickerRepresentation(
+    val id: String,
+    val name: String,
+    @DrawableRes val iconResourceId: Int,
+)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
similarity index 61%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
index f79ca10..86f2756 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
@@ -12,17 +12,17 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
+ *
  */
 
-package com.android.wm.shell.floating;
-
-import android.content.Intent;
+package com.android.systemui.keyguard.shared.model
 
 /**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Representation of a quick affordance slot (or position) for use to build "picker", "selector", or
+ * "settings" experiences.
  */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
-}
+data class KeyguardSlotPickerRepresentation(
+    val id: String,
+    /** The maximum number of selected affordances that can be present on this slot. */
+    val maxSelectedAffordances: Int = 1,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0ca3582..732a6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -21,10 +21,11 @@
     val to: KeyguardState = KeyguardState.NONE,
     val value: Float = 0f, // constrained [0.0, 1.0]
     val transitionState: TransitionState = TransitionState.FINISHED,
+    val ownerName: String = "",
 ) {
     constructor(
         info: TransitionInfo,
         value: Float,
         transitionState: TransitionState,
-    ) : this(info.from, info.to, value, transitionState)
+    ) : this(info.from, info.to, value, transitionState, info.ownerName)
 }
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 2c99ca5..3276b6d 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
@@ -27,6 +27,8 @@
 import androidx.core.view.updateLayoutParams
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconViewController
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
@@ -69,6 +71,11 @@
 
         /** Notifies that device configuration has changed. */
         fun onConfigurationChanged()
+
+        /**
+         * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+         */
+        fun shouldConstrainToTopOfLockIcon(): Boolean
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
@@ -208,6 +215,9 @@
             override fun onConfigurationChanged() {
                 configurationBasedDimensions.value = loadFromResources(view)
             }
+
+            override fun shouldConstrainToTopOfLockIcon(): Boolean =
+                    viewModel.shouldConstrainToTopOfLockIcon()
         }
     }
 
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 b6b2304..227796f 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
@@ -90,6 +90,12 @@
             .distinctUntilChanged()
     }
 
+    /**
+     * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+     */
+    fun shouldConstrainToTopOfLockIcon(): Boolean =
+            bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9a92843..0781600 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -19,7 +19,7 @@
 import android.view.View
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -34,7 +34,7 @@
 @Inject
 constructor(
     private val view: BouncerView,
-    private val interactor: BouncerInteractor,
+    private val interactor: PrimaryBouncerInteractor,
 ) {
     /** Observe on bouncer expansion amount. */
     val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
diff --git a/core/java/com/android/internal/app/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
similarity index 88%
rename from core/java/com/android/internal/app/LogAccessDialogActivity.java
rename to packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index 4adb867..a88a4ca 100644
--- a/core/java/com/android/internal/app/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.app;
+package com.android.systemui.logcat;
 
 import android.annotation.StyleRes;
 import android.app.Activity;
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -43,7 +42,9 @@
 import android.widget.Button;
 import android.widget.TextView;
 
-import com.android.internal.R;
+import com.android.internal.app.ILogAccessDialogCallback;
+import com.android.systemui.R;
+
 
 /**
  * Dialog responsible for obtaining user consent per-use log access
@@ -93,10 +94,7 @@
         mAlertLearnMore = getResources().getString(R.string.log_access_confirmation_learn_more);
 
         // create View
-        boolean isDarkTheme = (getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
-        int themeId = isDarkTheme ? android.R.style.Theme_DeviceDefault_Dialog_Alert :
-                android.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
+        int themeId = R.style.LogAccessDialogTheme;
         mAlertView = createView(themeId);
 
         // create AlertDialog
@@ -177,8 +175,7 @@
                 PackageManager.MATCH_DIRECT_BOOT_AUTO,
                 UserHandle.getUserId(uid)).loadLabel(pm);
 
-        String titleString = context.getString(
-                com.android.internal.R.string.log_access_confirmation_title, appLabel);
+        String titleString = context.getString(R.string.log_access_confirmation_title, appLabel);
 
         return titleString;
     }
@@ -235,15 +232,12 @@
     @Override
     public void onClick(View view) {
         try {
-            switch (view.getId()) {
-                case R.id.log_access_dialog_allow_button:
-                    mCallback.approveAccessForClient(mUid, mPackageName);
-                    finish();
-                    break;
-                case R.id.log_access_dialog_deny_button:
-                    declineLogAccess();
-                    finish();
-                    break;
+            if (view.getId() == R.id.log_access_dialog_allow_button) {
+                mCallback.approveAccessForClient(mUid, mPackageName);
+                finish();
+            } else if (view.getId() == R.id.log_access_dialog_allow_button) {
+                declineLogAccess();
+                finish();
             }
         } catch (RemoteException e) {
             finish();
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/OWNERS b/packages/SystemUI/src/com/android/systemui/logcat/OWNERS
new file mode 100644
index 0000000..9c0c414
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/logcat/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1218649
+file:platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index be357ee..ceb4845 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -79,8 +79,7 @@
         val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
         intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
 
-        // TODO(b/240939253): update copies
-        val title = getString(R.string.media_projection_dialog_service_title)
+        val title = getString(R.string.media_projection_permission_app_selector_title)
         intent.putExtra(Intent.EXTRA_TITLE, title)
         super.onCreate(bundle)
         controller.init()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
index a7ed69a..cacb3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
@@ -29,7 +29,6 @@
 
     private val smartspaceMediaTargetListeners: MutableList<SmartspaceTargetListener> =
         mutableListOf()
-    private var smartspaceMediaTargets: List<SmartspaceTarget> = listOf()
 
     override fun registerListener(smartspaceTargetListener: SmartspaceTargetListener) {
         smartspaceMediaTargetListeners.add(smartspaceTargetListener)
@@ -41,22 +40,7 @@
 
     /** Updates Smartspace data and propagates it to any listeners. */
     override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
-        // Filter out non-media targets.
-        val mediaTargets = mutableListOf<SmartspaceTarget>()
-        for (target in targets) {
-            val smartspaceTarget = target
-            if (smartspaceTarget.featureType == SmartspaceTarget.FEATURE_MEDIA) {
-                mediaTargets.add(smartspaceTarget)
-            }
-        }
-
-        if (!mediaTargets.isEmpty()) {
-            Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets")
-        }
-
-        smartspaceMediaTargets = mediaTargets
-        smartspaceMediaTargetListeners.forEach {
-            it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
-        }
+        Log.d(TAG, "Forwarding Smartspace updates $targets")
+        smartspaceMediaTargetListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e38c1ba..baeee9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -610,7 +610,11 @@
             // are
             // elements in mediaPlayers.
             if (MediaPlayerData.players().size != mediaContent.childCount) {
-                Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+                throw IllegalStateException(
+                    "Size of players list and number of views in carousel are out of sync. " +
+                        "Players size is ${MediaPlayerData.players().size}. " +
+                        "View count is ${mediaContent.childCount}."
+                )
             }
             return existingPlayer == null
         }
@@ -667,7 +671,11 @@
             // are
             // elements in mediaPlayers.
             if (MediaPlayerData.players().size != mediaContent.childCount) {
-                Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+                throw IllegalStateException(
+                    "Size of players list and number of views in carousel are out of sync. " +
+                        "Players size is ${MediaPlayerData.players().size}. " +
+                        "View count is ${mediaContent.childCount}."
+                )
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 662d059..8bddffc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
 import javax.inject.Inject
 
 /**
@@ -68,6 +69,7 @@
         private val mediaTttFlags: MediaTttFlags,
         private val uiEventLogger: MediaTttReceiverUiEventLogger,
         private val viewUtil: ViewUtil,
+        wakeLockBuilder: WakeLock.Builder,
 ) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
         context,
         logger,
@@ -77,6 +79,7 @@
         configurationController,
         powerManager,
         R.layout.media_ttt_chip_receiver,
+        wakeLockBuilder,
 ) {
     @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
     override val windowLayoutParams = commonWindowLayoutParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
new file mode 100644
index 0000000..1324d2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.motiontool
+
+import android.view.WindowManagerGlobal
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.app.motiontool.MotionToolManager
+import com.android.app.viewcapture.ViewCapture
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface MotionToolModule {
+
+    companion object {
+
+        @Provides
+        fun provideDdmHandleMotionTool(motionToolManager: MotionToolManager): DdmHandleMotionTool {
+            return DdmHandleMotionTool.getInstance(motionToolManager)
+        }
+
+        @Provides
+        fun provideMotionToolManager(
+            viewCapture: ViewCapture,
+            windowManagerGlobal: WindowManagerGlobal
+        ): MotionToolManager {
+            return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+        }
+
+        @Provides
+        fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
+
+        @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
+    }
+
+    @Binds
+    @IntoMap
+    @ClassKey(MotionToolStartable::class)
+    fun bindMotionToolStartable(impl: MotionToolStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
new file mode 100644
index 0000000..fbb9538
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.motiontool
+
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class MotionToolStartable
+@Inject
+internal constructor(private val ddmHandleMotionTool: DdmHandleMotionTool) : CoreStartable {
+
+    override fun start() {
+        ddmHandleMotionTool.register()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 813d2a0..b9f5859 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -87,7 +87,6 @@
 import android.view.HapticFeedbackConstants;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -99,6 +98,7 @@
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -1085,7 +1085,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         if (displayId != mDisplayId) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..eb87ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -49,8 +49,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -355,7 +355,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName,
+            @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
         boolean nbModeChanged = false;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d247f24..b964b76 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,7 +22,7 @@
 import android.view.KeyEvent
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.kotlin.getOrNull
-import com.android.wm.shell.floating.FloatingTasks
+import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
 import javax.inject.Inject
 
@@ -39,7 +39,7 @@
 constructor(
     private val context: Context,
     private val intentResolver: NoteTaskIntentResolver,
-    private val optionalFloatingTasks: Optional<FloatingTasks>,
+    private val optionalBubbles: Optional<Bubbles>,
     private val optionalKeyguardManager: Optional<KeyguardManager>,
     private val optionalUserManager: Optional<UserManager>,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
@@ -54,7 +54,7 @@
     }
 
     private fun showNoteTask() {
-        val floatingTasks = optionalFloatingTasks.getOrNull() ?: return
+        val bubbles = optionalBubbles.getOrNull() ?: return
         val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
         val intent = intentResolver.resolveIntent() ?: return
@@ -66,7 +66,7 @@
             context.startActivity(intent)
         } else {
             // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
-            floatingTasks.showOrSetStashed(intent)
+            bubbles.showAppBubble(intent)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d84717d..0a5b600 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.notetask
 
 import com.android.systemui.statusbar.CommandQueue
-import com.android.wm.shell.floating.FloatingTasks
+import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
@@ -26,7 +26,7 @@
 internal class NoteTaskInitializer
 @Inject
 constructor(
-    private val optionalFloatingTasks: Optional<FloatingTasks>,
+    private val optionalBubbles: Optional<Bubbles>,
     private val lazyNoteTaskController: Lazy<NoteTaskController>,
     private val commandQueue: CommandQueue,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
@@ -40,7 +40,7 @@
         }
 
     fun initialize() {
-        if (isEnabled && optionalFloatingTasks.isPresent) {
+        if (isEnabled && optionalBubbles.isPresent) {
             commandQueue.addCallback(callbacks)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index ef87fb4..dc9dcc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -29,6 +29,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.util.LargeScreenUtils;
 
 import java.io.PrintWriter;
 
@@ -52,6 +53,7 @@
     private boolean mQsDisabled;
     private int mContentHorizontalPadding = -1;
     private boolean mClippingEnabled;
+    private boolean mUseCombinedHeaders;
 
     public QSContainerImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -66,6 +68,10 @@
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
+    void setUseCombinedHeaders(boolean useCombinedHeaders) {
+        mUseCombinedHeaders = useCombinedHeaders;
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
@@ -143,9 +149,15 @@
 
     void updateResources(QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController) {
+        int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+        if (mUseCombinedHeaders
+                && !LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
+            topPadding = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+        }
         mQSPanelContainer.setPaddingRelative(
                 mQSPanelContainer.getPaddingStart(),
-                QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
+                topPadding,
                 mQSPanelContainer.getPaddingEnd(),
                 mQSPanelContainer.getPaddingBottom());
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index dea7bb5..28b4c822 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -22,6 +22,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -37,7 +39,6 @@
     private final ConfigurationController mConfigurationController;
     private final FalsingManager mFalsingManager;
     private final NonInterceptingScrollView mQSPanelContainer;
-
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
         @Override
@@ -65,13 +66,15 @@
             QSPanelController qsPanelController,
             QuickStatusBarHeaderController quickStatusBarHeaderController,
             ConfigurationController configurationController,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            FeatureFlags featureFlags) {
         super(view);
         mQsPanelController = qsPanelController;
         mQuickStatusBarHeaderController = quickStatusBarHeaderController;
         mConfigurationController = configurationController;
         mFalsingManager = falsingManager;
         mQSPanelContainer = mView.getQSPanelContainer();
+        view.setUseCombinedHeaders(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index abc0ade..64962b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -237,7 +237,7 @@
      * @return if bouncer is in transit
      */
     public boolean isBouncerInTransit() {
-        return mStatusBarKeyguardViewManager.isBouncerInTransit();
+        return mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 27d9da6..946fe54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -288,8 +288,15 @@
         }
 
         MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
-        qqsLP.topMargin = largeScreenHeaderActive || !mUseCombinedQSHeader ? mContext.getResources()
-                .getDimensionPixelSize(R.dimen.qqs_layout_margin_top) : qsOffsetHeight;
+        if (largeScreenHeaderActive) {
+            qqsLP.topMargin = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
+        } else if (!mUseCombinedQSHeader) {
+            qqsLP.topMargin = qsOffsetHeight;
+        } else {
+            qqsLP.topMargin = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
+        }
         mHeaderQsPanel.setLayoutParams(qqsLP);
 
         updateBatteryMode();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 46c4f41..ba97297 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -108,7 +108,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
@@ -470,8 +469,6 @@
     };
 
     private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
-    private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
-            this::notifySplitScreenBoundsChanged;
 
     // This is the death handler for the binder from the launcher service
     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
@@ -839,26 +836,6 @@
         }
     }
 
-    /**
-     * Notifies the Launcher of split screen size changes
-     *
-     * @param secondaryWindowBounds Bounds of the secondary window including the insets
-     * @param secondaryWindowInsets stable insets received by the secondary window
-     */
-    public void notifySplitScreenBoundsChanged(
-            Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
-        try {
-            if (mOverviewProxy != null) {
-                mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
-                        secondaryWindowBounds, secondaryWindowInsets);
-            } else {
-                Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
-        }
-    }
-
     private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
         /**
          * Notifies the Launcher that screen turned on and ready to use
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
new file mode 100644
index 0000000..731b177
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.sensorprivacy.television;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+
+import android.annotation.DimenRes;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorPrivacyManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.tv.TvBottomSheetActivity;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import javax.inject.Inject;
+
+/**
+ * Bottom sheet that is shown when the camera/mic sensors privacy state changed
+ * by the global software toggle or physical privacy switch.
+ */
+public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
+
+    private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
+
+    private static final int ALL_SENSORS = Integer.MAX_VALUE;
+
+    private int mSensor = -1;
+    private int mToggleType = -1;
+
+    private final GlobalSettings mGlobalSettings;
+    private final IndividualSensorPrivacyController mSensorPrivacyController;
+    private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
+    private TextView mTitle;
+    private TextView mContent;
+    private ImageView mIcon;
+    private ImageView mSecondIcon;
+    private Button mPositiveButton;
+    private Button mCancelButton;
+
+    @Inject
+    public TvSensorPrivacyChangedActivity(
+            IndividualSensorPrivacyController individualSensorPrivacyController,
+            GlobalSettings globalSettings) {
+        mSensorPrivacyController = individualSensorPrivacyController;
+        mGlobalSettings = globalSettings;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+        boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
+                false);
+        if (allSensors) {
+            mSensor = ALL_SENSORS;
+        } else {
+            mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
+        }
+
+        mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
+
+        if (mSensor == -1 || mToggleType == -1) {
+            Log.v(TAG, "Invalid extras");
+            finish();
+            return;
+        }
+
+        // Do not show for software toggles
+        if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
+            finish();
+            return;
+        }
+
+        mSensorPrivacyCallback = (sensor, blocked) -> {
+            updateUI();
+        };
+
+        initUI();
+    }
+
+    private void initUI() {
+        mTitle = findViewById(R.id.bottom_sheet_title);
+        mContent = findViewById(R.id.bottom_sheet_body);
+        mIcon = findViewById(R.id.bottom_sheet_icon);
+        // mic icon if both icons are shown
+        mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
+        mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
+        mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+        mCancelButton.setText(android.R.string.cancel);
+        mCancelButton.setOnClickListener(v -> finish());
+
+        updateUI();
+    }
+
+    private void updateUI() {
+        final Resources resources = getResources();
+        setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
+        setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
+
+        switch (mSensor) {
+            case CAMERA:
+                updateUiForCameraUpdate(
+                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
+                break;
+            case MICROPHONE:
+            default:
+                updateUiForMicUpdate(
+                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
+                break;
+        }
+
+        // Start animation if drawable is animated
+        Drawable iconDrawable = mIcon.getDrawable();
+        if (iconDrawable instanceof Animatable) {
+            ((Animatable) iconDrawable).start();
+        }
+
+        mPositiveButton.setVisibility(View.GONE);
+        mCancelButton.setText(android.R.string.ok);
+    }
+
+    private void updateUiForMicUpdate(boolean blocked) {
+        if (blocked) {
+            mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
+            if (isExplicitUserInteractionAudioBypassAllowed()) {
+                mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
+            } else {
+                mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
+            }
+            mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
+            mSecondIcon.setVisibility(View.GONE);
+        } else {
+            mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
+            mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
+            mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
+            mSecondIcon.setVisibility(View.GONE);
+        }
+    }
+
+    private void updateUiForCameraUpdate(boolean blocked) {
+        if (blocked) {
+            mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
+            mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
+            mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
+            mSecondIcon.setVisibility(View.GONE);
+        } else {
+            mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
+            mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
+            mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
+            mSecondIcon.setVisibility(View.GONE);
+        }
+    }
+
+    private void setIconTint(boolean enableTint) {
+        final Resources resources = getResources();
+
+        if (enableTint) {
+            final ColorStateList iconTint = resources.getColorStateList(
+                    R.color.bottom_sheet_icon_color, getTheme());
+            mIcon.setImageTintList(iconTint);
+            mSecondIcon.setImageTintList(iconTint);
+        } else {
+            mIcon.setImageTintList(null);
+            mSecondIcon.setImageTintList(null);
+        }
+
+        mIcon.invalidate();
+        mSecondIcon.invalidate();
+    }
+
+    private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
+        final Resources resources = getResources();
+        final int iconWidth = resources.getDimensionPixelSize(widthRes);
+        final int iconHeight = resources.getDimensionPixelSize(heightRes);
+
+        mIcon.getLayoutParams().width = iconWidth;
+        mIcon.getLayoutParams().height = iconHeight;
+        mIcon.invalidate();
+
+        mSecondIcon.getLayoutParams().width = iconWidth;
+        mSecondIcon.getLayoutParams().height = iconHeight;
+        mSecondIcon.invalidate();
+    }
+
+    private boolean isExplicitUserInteractionAudioBypassAllowed() {
+        return mGlobalSettings.getInt(
+                Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateUI();
+        mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
+    }
+
+    @Override
+    public void onPause() {
+        mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
+        super.onPause();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index d543eb2..1b9657f 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -16,17 +16,23 @@
 
 package com.android.systemui.sensorprivacy.television;
 
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.hardware.SensorPrivacyManager.Sources.OTHER;
 
 import android.annotation.DimenRes;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.hardware.SensorPrivacyManager;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
@@ -39,6 +45,8 @@
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.tv.TvBottomSheetActivity;
 
+import java.util.List;
+
 import javax.inject.Inject;
 
 /**
@@ -57,6 +65,8 @@
 
     private int mSensor = -1;
 
+    private final AppOpsManager mAppOpsManager;
+    private final RoleManager mRoleManager;
     private final IndividualSensorPrivacyController mSensorPrivacyController;
     private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
     private TextView mTitle;
@@ -68,8 +78,11 @@
 
     @Inject
     public TvUnblockSensorActivity(
-            IndividualSensorPrivacyController individualSensorPrivacyController) {
+            IndividualSensorPrivacyController individualSensorPrivacyController,
+            AppOpsManager appOpsManager, RoleManager roleManager) {
         mSensorPrivacyController = individualSensorPrivacyController;
+        mAppOpsManager = appOpsManager;
+        mRoleManager = roleManager;
     }
 
     @Override
@@ -120,6 +133,10 @@
                 toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
                 break;
         }
+        showToastAndFinish(toastMsgResId);
+    }
+
+    private void showToastAndFinish(int toastMsgResId) {
         Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
         finish();
     }
@@ -149,7 +166,9 @@
     }
 
     private void updateUI() {
-        if (isBlockedByHardwareToggle()) {
+        if (isHTTAccessDisabled()) {
+            updateUiForHTT();
+        } else if (isBlockedByHardwareToggle()) {
             updateUiForHardwareToggle();
         } else {
             updateUiForSoftwareToggle();
@@ -208,20 +227,20 @@
 
         switch (mSensor) {
             case MICROPHONE:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
                 mSecondIcon.setVisibility(View.GONE);
                 break;
             case CAMERA:
-                mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
                 mSecondIcon.setVisibility(View.GONE);
                 break;
             case ALL_SENSORS:
             default:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
                 mSecondIcon.setImageResource(
@@ -241,6 +260,29 @@
         });
     }
 
+    private void updateUiForHTT() {
+        setIconTint(true);
+        setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
+
+        mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
+        mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
+        mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
+        mSecondIcon.setVisibility(View.GONE);
+
+        mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
+        mPositiveButton.setOnClickListener(v -> {
+            Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
+            ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
+                    MATCH_SYSTEM_ONLY);
+            if (activityInfo == null) {
+                showToastAndFinish(com.android.internal.R.string.noApplications);
+            } else {
+                startActivity(openPrivacySettings);
+                finish();
+            }
+        });
+    }
+
     private void setIconTint(boolean enableTint) {
         final Resources resources = getResources();
 
@@ -272,6 +314,18 @@
         mSecondIcon.invalidate();
     }
 
+    private boolean isHTTAccessDisabled() {
+        String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
+        if (!assistantPkgs.contains(pkg)) {
+            return false;
+        }
+
+        return (mAppOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
+                pkg) != AppOpsManager.MODE_ALLOWED);
+    }
+
     @Override
     public void onResume() {
         super.onResume();
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index cd5647e..200288b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -107,6 +107,7 @@
 
         val filter = IntentFilter().apply {
             addAction(Intent.ACTION_USER_SWITCHED)
+            addAction(Intent.ACTION_USER_INFO_CHANGED)
             // These get called when a managed profile goes in or out of quiet mode.
             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
             addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
@@ -124,6 +125,7 @@
             Intent.ACTION_USER_SWITCHED -> {
                 handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
             }
+            Intent.ACTION_USER_INFO_CHANGED,
             Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
             Intent.ACTION_MANAGED_PROFILE_REMOVED,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
new file mode 100644
index 0000000..ae303eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade;
+
+import android.annotation.NonNull;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Drawable for NotificationPanelViewController.
+ */
+public class DebugDrawable extends Drawable {
+
+    private final NotificationPanelViewController mNotificationPanelViewController;
+    private final NotificationPanelView mView;
+    private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
+    private final LockIconViewController mLockIconViewController;
+    private final Set<Integer> mDebugTextUsedYPositions;
+    private final Paint mDebugPaint;
+
+    public DebugDrawable(
+            NotificationPanelViewController notificationPanelViewController,
+            NotificationPanelView notificationPanelView,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            LockIconViewController lockIconViewController
+    ) {
+        mNotificationPanelViewController = notificationPanelViewController;
+        mView = notificationPanelView;
+        mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
+        mLockIconViewController = lockIconViewController;
+        mDebugTextUsedYPositions = new HashSet<>();
+        mDebugPaint = new Paint();
+    }
+
+    @Override
+    public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
+        mDebugTextUsedYPositions.clear();
+
+        mDebugPaint.setColor(Color.RED);
+        mDebugPaint.setStrokeWidth(2);
+        mDebugPaint.setStyle(Paint.Style.STROKE);
+        mDebugPaint.setTextSize(24);
+        String headerDebugInfo = mNotificationPanelViewController.getHeaderDebugInfo();
+        if (headerDebugInfo != null) canvas.drawText(headerDebugInfo, 50, 100, mDebugPaint);
+
+        drawDebugInfo(canvas, mNotificationPanelViewController.getMaxPanelHeight(),
+                Color.RED, "getMaxPanelHeight()");
+        drawDebugInfo(canvas, (int) mNotificationPanelViewController.getExpandedHeight(),
+                Color.BLUE, "getExpandedHeight()");
+        drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+                Color.GREEN, "calculatePanelHeightQsExpanded()");
+        drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(),
+                Color.YELLOW, "calculatePanelHeightShade()");
+        drawDebugInfo(canvas,
+                (int) mNotificationPanelViewController.calculateNotificationsTopPadding(),
+                Color.MAGENTA, "calculateNotificationsTopPadding()");
+        drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY,
+                Color.GRAY, "mClockPositionResult.clockY");
+        drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
+                "mLockIconViewController.getTop()");
+
+        if (mNotificationPanelViewController.getKeyguardShowing()) {
+            // Notifications have the space between those two lines.
+            drawDebugInfo(canvas,
+                    mNotificationStackScrollLayoutController.getTop()
+                            + (int) mNotificationPanelViewController
+                            .getKeyguardNotificationTopPadding(),
+                    Color.RED, "NSSL.getTop() + mKeyguardNotificationTopPadding");
+
+            drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom()
+                            - (int) mNotificationPanelViewController
+                            .getKeyguardNotificationBottomPadding(),
+                    Color.RED, "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
+        }
+
+        mDebugPaint.setColor(Color.CYAN);
+        canvas.drawLine(0,
+                mNotificationPanelViewController.getClockPositionResult().stackScrollerPadding,
+                mView.getWidth(), mNotificationStackScrollLayoutController.getTopPadding(),
+                mDebugPaint);
+    }
+
+    private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
+        mDebugPaint.setColor(color);
+        canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
+                /* stopY= */ y, mDebugPaint);
+        canvas.drawText(label + " = " + y + "px", /* x= */ 0,
+                /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+    }
+
+    private int computeDebugYTextPosition(int lineY) {
+        if (lineY - mDebugPaint.getTextSize() < 0) {
+            // Avoiding drawing out of bounds
+            lineY += mDebugPaint.getTextSize();
+        }
+        int textY = lineY;
+        while (mDebugTextUsedYPositions.contains(textY)) {
+            textY = (int) (textY + mDebugPaint.getTextSize());
+        }
+        mDebugTextUsedYPositions.add(textY);
+        return textY;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.UNKNOWN;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 6b540aa..63d0d16 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -246,6 +246,8 @@
             qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
             if (header is MotionLayout) {
                 loadConstraints()
+                header.minHeight = resources
+                        .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
                 lastInsets?.let { updateConstraintsForInsets(header, it) }
             }
             updateResources()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3a624ca..5a963ce 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -56,15 +56,10 @@
 import android.content.ContentResolver;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.ColorFilter;
 import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.SensorLocationInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
@@ -229,10 +224,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
-import java.util.Set;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -455,7 +448,6 @@
      * need to take this into account in our panel height calculation.
      */
     private boolean mQsAnimatorExpand;
-    private boolean mIsLaunchTransitionFinished;
     private ValueAnimator mQsSizeChangeAnimator;
     private boolean mQsScrimEnabled = true;
     private boolean mQsTouchAboveFalsingThreshold;
@@ -900,7 +892,8 @@
         mView.setOnApplyWindowInsetsListener((v, insets) -> onApplyShadeWindowInsets(insets));
 
         if (DEBUG_DRAWABLE) {
-            mView.getOverlay().add(new DebugDrawable());
+            mView.getOverlay().add(new DebugDrawable(this, mView,
+                    mNotificationStackScrollLayoutController, mLockIconViewController));
         }
 
         mKeyguardUnfoldTransition = unfoldComponent.map(
@@ -1153,8 +1146,15 @@
 
         mLargeScreenShadeHeaderHeight =
                 mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
-        mQuickQsHeaderHeight = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
-                SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+        // TODO: When the flag is eventually removed, it means that we have a single view that is
+        // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually
+        // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class
+        // that controls the view as the offset needs to be the same regardless.
+        if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) {
+            mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
+        } else {
+            mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
+        }
         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
         mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
@@ -1309,7 +1309,11 @@
     }
 
     private void initBottomArea() {
-        mKeyguardBottomArea.init(mKeyguardBottomAreaViewModel, mFalsingManager);
+        mKeyguardBottomArea.init(
+                mKeyguardBottomAreaViewModel,
+                mFalsingManager,
+                mLockIconViewController
+        );
     }
 
     @VisibleForTesting
@@ -1506,6 +1510,10 @@
         updateClock();
     }
 
+    public KeyguardClockPositionAlgorithm.Result getClockPositionResult() {
+        return mClockPositionResult;
+    }
+
     @ClockSize
     private int computeDesiredClockSize() {
         if (mSplitShadeEnabled) {
@@ -1753,7 +1761,6 @@
     }
 
     public void resetViews(boolean animate) {
-        mIsLaunchTransitionFinished = false;
         mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         if (animate && !isFullyCollapsed()) {
@@ -2971,7 +2978,7 @@
         }
     }
 
-    private float calculateNotificationsTopPadding() {
+    float calculateNotificationsTopPadding() {
         if (mSplitShadeEnabled) {
             return mKeyguardShowing ? getKeyguardNotificationStaticPadding() : 0;
         }
@@ -3005,6 +3012,18 @@
         }
     }
 
+    public boolean getKeyguardShowing() {
+        return mKeyguardShowing;
+    }
+
+    public float getKeyguardNotificationTopPadding() {
+        return mKeyguardNotificationTopPadding;
+    }
+
+    public float getKeyguardNotificationBottomPadding() {
+        return mKeyguardNotificationBottomPadding;
+    }
+
     /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
     private int getKeyguardNotificationStaticPadding() {
         if (!mKeyguardShowing) {
@@ -3274,7 +3293,6 @@
         return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
     }
 
-    @VisibleForTesting
     int getMaxPanelHeight() {
         int min = mStatusBarMinHeight;
         if (!(mBarState == KEYGUARD)
@@ -3400,7 +3418,7 @@
         }
     }
 
-    private int calculatePanelHeightQsExpanded() {
+    int calculatePanelHeightQsExpanded() {
         float
                 notificationHeight =
                 mNotificationStackScrollLayoutController.getHeight()
@@ -3779,10 +3797,6 @@
         mQs.closeCustomizer();
     }
 
-    public boolean isLaunchTransitionFinished() {
-        return mIsLaunchTransitionFinished;
-    }
-
     public void setIsLaunchAnimationRunning(boolean running) {
         boolean wasRunning = mIsLaunchAnimationRunning;
         mIsLaunchAnimationRunning = running;
@@ -4368,6 +4382,10 @@
         if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
     }
 
+    public String getHeaderDebugInfo() {
+        return mHeaderDebugInfo;
+    }
+
     public void onThemeChanged() {
         mConfigurationListener.onThemeChanged();
     }
@@ -4643,7 +4661,7 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (!mCentralSurfaces.isBouncerShowing()
-                && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
+                && !mStatusBarKeyguardViewManager.isShowingAlternateBouncer()
                 && !mKeyguardStateController.isKeyguardGoingAway()) {
             onEmptySpaceClick();
             onTrackingStopped(true);
@@ -4817,7 +4835,6 @@
         setExpandedHeight(getMaxPanelTransitionDistance() * frac);
     }
 
-    @VisibleForTesting
     float getExpandedHeight() {
         return mExpandedHeight;
     }
@@ -5519,89 +5536,6 @@
         }
     }
 
-    private final class DebugDrawable extends Drawable {
-        private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
-        private final Paint mDebugPaint = new Paint();
-
-        @Override
-        public void draw(@NonNull Canvas canvas) {
-            mDebugTextUsedYPositions.clear();
-
-            mDebugPaint.setColor(Color.RED);
-            mDebugPaint.setStrokeWidth(2);
-            mDebugPaint.setStyle(Paint.Style.STROKE);
-            mDebugPaint.setTextSize(24);
-            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, mDebugPaint);
-
-            drawDebugInfo(canvas, getMaxPanelHeight(), Color.RED, "getMaxPanelHeight()");
-            drawDebugInfo(canvas, (int) getExpandedHeight(), Color.BLUE, "getExpandedHeight()");
-            drawDebugInfo(canvas, calculatePanelHeightQsExpanded(), Color.GREEN,
-                    "calculatePanelHeightQsExpanded()");
-            drawDebugInfo(canvas, calculatePanelHeightShade(), Color.YELLOW,
-                    "calculatePanelHeightShade()");
-            drawDebugInfo(canvas, (int) calculateNotificationsTopPadding(), Color.MAGENTA,
-                    "calculateNotificationsTopPadding()");
-            drawDebugInfo(canvas, mClockPositionResult.clockY, Color.GRAY,
-                    "mClockPositionResult.clockY");
-            drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
-                    "mLockIconViewController.getTop()");
-
-            if (mKeyguardShowing) {
-                // Notifications have the space between those two lines.
-                drawDebugInfo(canvas,
-                        mNotificationStackScrollLayoutController.getTop() +
-                                (int) mKeyguardNotificationTopPadding,
-                        Color.RED,
-                        "NSSL.getTop() + mKeyguardNotificationTopPadding");
-
-                drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() -
-                                (int) mKeyguardNotificationBottomPadding,
-                        Color.RED,
-                        "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
-            }
-
-            mDebugPaint.setColor(Color.CYAN);
-            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
-                    mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
-        }
-
-        private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
-            mDebugPaint.setColor(color);
-            canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
-                    /* stopY= */ y, mDebugPaint);
-            canvas.drawText(label + " = " + y + "px", /* x= */ 0,
-                    /* y= */ computeDebugYTextPosition(y), mDebugPaint);
-        }
-
-        private int computeDebugYTextPosition(int lineY) {
-            if (lineY - mDebugPaint.getTextSize() < 0) {
-                // Avoiding drawing out of bounds
-                lineY += mDebugPaint.getTextSize();
-            }
-            int textY = lineY;
-            while (mDebugTextUsedYPositions.contains(textY)) {
-                textY = (int) (textY + mDebugPaint.getTextSize());
-            }
-            mDebugTextUsedYPositions.add(textY);
-            return textY;
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.UNKNOWN;
-        }
-    }
-
     @NonNull
     private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) {
         // the same types of insets that are handled in NotificationShadeWindowView
@@ -6111,7 +6045,7 @@
                     == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
                     || action
                     == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
-                mStatusBarKeyguardViewManager.showBouncer(true);
+                mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
                 return true;
             }
             return super.performAccessibilityAction(host, action, args);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 1e63b2d..bb67280c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@
                     return true;
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                     // capture all touches if the alt auth bouncer is showing
                     return true;
                 }
@@ -322,7 +322,7 @@
                     handled = !mService.isPulsing();
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                     // eat the touch
                     handled = true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 73c6d50..85b259e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.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.systemui.shade
 
 import android.view.View
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 7615301..40ed40a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.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.systemui.shade
 
 import android.view.MotionEvent
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
index f4db3ab..8847dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.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.systemui.shade.transition
 
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index a77c21a..218e897 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.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.systemui.shade.transition
 
 import android.content.res.Configuration
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
index 22e847d..a4642e0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.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.systemui.shade.transition
 
 import com.android.systemui.shade.PanelState
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1e8208f..1054aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.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.systemui.shade.transition
 
 import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index 8c57194..fde08ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.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.systemui.shade.transition
 
 import android.animation.Animator
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index b821a2f..f786ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,7 +53,7 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -356,11 +356,11 @@
         default void onRecentsAnimationStateChanged(boolean running) { }
 
         /**
-         * @see IStatusBar#onSystemBarAttributesChanged.
+         * @see IStatusBar#onSystemBarAttributesChanged
          */
         default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) { }
 
         /**
@@ -1090,7 +1090,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
@@ -1099,7 +1099,7 @@
             args.argi3 = navbarColorManagedByIme ? 1 : 0;
             args.arg1 = appearanceRegions;
             args.argi4 = behavior;
-            args.arg2 = requestedVisibilities;
+            args.argi5 = requestedVisibleTypes;
             args.arg3 = packageName;
             args.arg4 = letterboxDetails;
             mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
@@ -1582,8 +1582,7 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
                                 (AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
-                                (InsetsVisibilities) args.arg2, (String) args.arg3,
-                                (LetterboxDetails[]) args.arg4);
+                                args.argi5, (String) args.arg3, (LetterboxDetails[]) args.arg4);
                     }
                     args.recycle();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 073ab8b..3670d09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -21,6 +21,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
 import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
@@ -50,7 +51,6 @@
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
@@ -74,12 +74,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.ViewClippingUtil;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
@@ -138,6 +138,7 @@
     private final KeyguardStateController mKeyguardStateController;
     protected final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final AuthController mAuthController;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -188,14 +189,7 @@
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
 
     private boolean mDozing;
-    private final ViewClippingUtil.ClippingParameters mClippingParams =
-            new ViewClippingUtil.ClippingParameters() {
-                @Override
-                public boolean shouldFinish(View view) {
-                    return view == mIndicationArea;
-                }
-            };
-    private ScreenLifecycle mScreenLifecycle;
+    private final ScreenLifecycle mScreenLifecycle;
     private final ScreenLifecycle.Observer mScreenObserver =
             new ScreenLifecycle.Observer() {
         @Override
@@ -209,6 +203,7 @@
             }
         }
     };
+    private boolean mFaceLockedOutThisAuthSession;
 
     /**
      * Creates a new KeyguardIndicationController and registers callbacks.
@@ -229,6 +224,7 @@
             @Main DelayableExecutor executor,
             @Background DelayableExecutor bgExecutor,
             FalsingManager falsingManager,
+            AuthController authController,
             LockPatternUtils lockPatternUtils,
             ScreenLifecycle screenLifecycle,
             KeyguardBypassController keyguardBypassController,
@@ -248,6 +244,7 @@
         mExecutor = executor;
         mBackgroundExecutor = bgExecutor;
         mLockPatternUtils = lockPatternUtils;
+        mAuthController = authController;
         mFalsingManager = falsingManager;
         mKeyguardBypassController = keyguardBypassController;
         mAccessibilityManager = accessibilityManager;
@@ -619,7 +616,6 @@
                                 if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
                                     return;
                                 }
-                                int currentUserId = getCurrentUser();
                                 mDevicePolicyManager.logoutUser();
                             })
                             .build(),
@@ -676,7 +672,7 @@
                 hideTransientIndication();
             }
             updateDeviceEntryIndication(false);
-        } else if (!visible) {
+        } else {
             // If we unlock and return to keyguard quickly, previous error should not be shown
             hideTransientIndication();
         }
@@ -764,7 +760,7 @@
      * logic.
      */
     private void showBiometricMessage(CharSequence biometricMessage,
-            CharSequence biometricMessageFollowUp) {
+            @Nullable CharSequence biometricMessageFollowUp) {
         if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
             return;
         }
@@ -929,7 +925,7 @@
         }
 
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-            if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+            if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
                 return; // udfps affordance is highlighted, no need to show action to unlock
             } else if (mKeyguardUpdateMonitor.isFaceEnrolled()) {
                 String message = mContext.getString(R.string.keyguard_retry);
@@ -1072,17 +1068,12 @@
                     && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
             final boolean faceAuthFailed = biometricSourceType == FACE
                     && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
-            final boolean isUnlockWithFingerprintPossible =
-                    mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                            getCurrentUser());
+            final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
             final boolean isCoExFaceAcquisitionMessage =
                     faceAuthSoftError && isUnlockWithFingerprintPossible;
             if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
-                if (DEBUG) {
-                    Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString
-                            + ", due to co-ex logic");
-                }
-                return;
+                debugLog("skip showing msgId=" + msgId + " helpString=" + helpString
+                        + ", due to co-ex logic");
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
                         mInitialTextColorState);
@@ -1120,74 +1111,45 @@
         }
 
         @Override
-        public void onBiometricError(int msgId, String errString,
-                BiometricSourceType biometricSourceType) {
-            CharSequence deferredFaceMessage = null;
-            if (biometricSourceType == FACE) {
-                if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
-                    deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
-                    if (DEBUG) {
-                        Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage);
-                    }
-                }
-                mFaceAcquiredMessageDeferral.reset();
-            }
-
-            if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
-                if (DEBUG) {
-                    Log.d(TAG, "suppressingBiometricError msgId=" + msgId
-                            + " source=" + biometricSourceType);
-                }
-            } else if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_TIMEOUT) {
-                // Co-ex: show deferred message OR nothing
-                if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                        KeyguardUpdateMonitor.getCurrentUser())) {
-                    // if we're on the lock screen (bouncer isn't showing), show the deferred msg
-                    if (deferredFaceMessage != null
-                            && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
-                        showBiometricMessage(
-                                deferredFaceMessage,
-                                mContext.getString(R.string.keyguard_suggest_fingerprint)
-                        );
-                        return;
-                    }
-
-                    // otherwise, don't show any message
-                    if (DEBUG) {
-                        Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
-                    }
-                    return;
-                }
-
-                // Face-only: The face timeout message is not very actionable, let's ask the user to
-                // manually retry.
-                if (deferredFaceMessage != null) {
-                    showBiometricMessage(
-                            deferredFaceMessage,
-                            mContext.getString(R.string.keyguard_unlock)
-                    );
-                } else {
-                    // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
-                    showActionToUnlock();
-                }
-            } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-                mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
-            } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
-                showBiometricMessage(errString);
-            } else {
-                mBiometricErrorMessageToShowOnScreenOn = errString;
+        public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
+            if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
+                mFaceLockedOutThisAuthSession = false;
             }
         }
 
-        private boolean shouldSuppressBiometricError(int msgId,
-                BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
-            if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
-                return shouldSuppressFingerprintError(msgId, updateMonitor);
-            }
+        @Override
+        public void onBiometricError(int msgId, String errString,
+                BiometricSourceType biometricSourceType) {
             if (biometricSourceType == FACE) {
-                return shouldSuppressFaceError(msgId, updateMonitor);
+                onFaceAuthError(msgId, errString);
+            } else if (biometricSourceType == FINGERPRINT) {
+                onFingerprintAuthError(msgId, errString);
             }
-            return false;
+        }
+
+        private void onFaceAuthError(int msgId, String errString) {
+            CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+            mFaceAcquiredMessageDeferral.reset();
+            if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
+                debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString);
+                return;
+            }
+            if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
+                handleFaceAuthTimeoutError(deferredFaceMessage);
+            } else if (isLockoutError(msgId)) {
+                handleFaceLockoutError(errString);
+            } else {
+                showErrorMessageNowOrLater(errString, null);
+            }
+        }
+
+        private void onFingerprintAuthError(int msgId, String errString) {
+            if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
+                debugLog("suppressingFingerprintError msgId=" + msgId
+                        + " errString= " + errString);
+            } else {
+                showErrorMessageNowOrLater(errString, null);
+            }
         }
 
         private boolean shouldSuppressFingerprintError(int msgId,
@@ -1197,7 +1159,7 @@
             // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
             // check of whether non-strong biometric is allowed
             return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
-                    && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+                    && !isLockoutError(msgId))
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
                     || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
@@ -1286,7 +1248,82 @@
         }
     }
 
-    private StatusBarStateController.StateListener mStatusBarStateListener =
+    private void handleFaceLockoutError(String errString) {
+        int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+                : R.string.keyguard_unlock;
+        String followupMessage = mContext.getString(followupMsgId);
+        // Lockout error can happen multiple times in a session because we trigger face auth
+        // even when it is locked out so that the user is aware that face unlock would have
+        // triggered but didn't because it is locked out.
+
+        // On first lockout we show the error message from FaceManager, which tells the user they
+        // had too many unsuccessful attempts.
+        if (!mFaceLockedOutThisAuthSession) {
+            mFaceLockedOutThisAuthSession = true;
+            showErrorMessageNowOrLater(errString, followupMessage);
+        } else if (!mAuthController.isUdfpsFingerDown()) {
+            // On subsequent lockouts, we show a more generic locked out message.
+            showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable),
+                    followupMessage);
+        }
+    }
+
+    private static boolean isLockoutError(int msgId) {
+        return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+                || msgId == FaceManager.FACE_ERROR_LOCKOUT;
+    }
+
+    private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
+        debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
+        if (canUnlockWithFingerprint()) {
+            // Co-ex: show deferred message OR nothing
+            // if we're on the lock screen (bouncer isn't showing), show the deferred msg
+            if (deferredFaceMessage != null
+                    && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+                showBiometricMessage(
+                        deferredFaceMessage,
+                        mContext.getString(R.string.keyguard_suggest_fingerprint)
+                );
+            } else {
+                // otherwise, don't show any message
+                debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+            }
+        } else if (deferredFaceMessage != null) {
+            // Face-only: The face timeout message is not very actionable, let's ask the
+            // user to manually retry.
+            showBiometricMessage(
+                    deferredFaceMessage,
+                    mContext.getString(R.string.keyguard_unlock)
+            );
+        } else {
+            // Face-only
+            // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
+            showActionToUnlock();
+        }
+    }
+
+    private boolean canUnlockWithFingerprint() {
+        return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+                KeyguardUpdateMonitor.getCurrentUser());
+    }
+
+    private void debugLog(String logMsg) {
+        if (DEBUG) {
+            Log.d(TAG, logMsg);
+        }
+    }
+
+    private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
+        if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+            mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
+        } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
+            showBiometricMessage(errString, followUpMsg);
+        } else {
+            mBiometricErrorMessageToShowOnScreenOn = errString;
+        }
+    }
+
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
             new StatusBarStateController.StateListener() {
         @Override
         public void onStateChanged(int newState) {
@@ -1307,7 +1344,7 @@
         }
     };
 
-    private KeyguardStateController.Callback mKeyguardStateCallback =
+    private final KeyguardStateController.Callback mKeyguardStateCallback =
             new KeyguardStateController.Callback() {
         @Override
         public void onUnlockedChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 408293c..815b86e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -805,7 +805,7 @@
         iconState.hidden = isAppearing
                 || (view instanceof ExpandableNotificationRow
                 && ((ExpandableNotificationRow) view).isLowPriority()
-                && mShelfIcons.hasMaxNumDot())
+                && mShelfIcons.areIconsOverflowing())
                 || (transitionAmount == 0.0f && !iconState.isAnimating(icon))
                 || row.isAboveShelf()
                 || row.showingPulsing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index c04bc82..fdec745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
@@ -34,9 +32,10 @@
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InsetsFlags;
-import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.animation.Interpolator;
@@ -497,9 +496,9 @@
 
     @Override
     public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName) {
-        boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
-                || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
+            @InsetsType int requestedVisibleTypes, String packageName) {
+        boolean isFullscreen = (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0
+                || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
         if (mIsFullscreen != isFullscreen) {
             mIsFullscreen = isFullscreen;
             synchronized (mListeners) {
@@ -514,12 +513,12 @@
         if (DEBUG_IMMERSIVE_APPS) {
             boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
             String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
-            String requestedVisibilityString = requestedVisibilities.toString();
-            if (requestedVisibilityString.isEmpty()) {
-                requestedVisibilityString = "none";
+            String requestedVisibleTypesString = WindowInsets.Type.toString(requestedVisibleTypes);
+            if (requestedVisibleTypesString.isEmpty()) {
+                requestedVisibleTypesString = "none";
             }
             Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
-                    + " requested visibilities: " + requestedVisibilityString);
+                    + " requested visible types: " + requestedVisibleTypesString);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2cc7738..1189107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,8 +19,8 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -154,7 +154,7 @@
      * Set the system bar attributes
      */
     void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName);
+            @InsetsType int requestedVisibleTypes, String packageName);
 
     /**
      * Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index eacb18e..14d0d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -300,7 +300,7 @@
 
             @Override
             public boolean isShowingAlternateAuthOnUnlock() {
-                return statusBarKeyguardViewManager.get().shouldShowAltAuth();
+                return statusBarKeyguardViewManager.get().canShowAlternateBouncer();
             }
         };
         return new DialogLaunchAnimator(callback, interactionJankMonitor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 470cbcb..5dbb4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -68,6 +69,7 @@
     private val mHeadsUpViewBinder: HeadsUpViewBinder,
     private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
     private val mRemoteInputManager: NotificationRemoteInputManager,
+    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
     @IncomingHeader private val mIncomingHeaderController: NodeController,
     @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
@@ -380,6 +382,12 @@
          * Notification was just added and if it should heads up, bind the view and then show it.
          */
         override fun onEntryAdded(entry: NotificationEntry) {
+            // First check whether this notification should launch a full screen intent, and
+            // launch it if needed.
+            if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+                mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+            }
+
             // shouldHeadsUp includes check for whether this notification should be filtered
             val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
             mPostedEntries[entry.key] = PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
new file mode 100644
index 0000000..74ff78e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/**
+ * A class that enables communication of decisions to launch a notification's full screen intent.
+ */
+@SysUISingleton
+class LaunchFullScreenIntentProvider @Inject constructor() {
+    companion object {
+        private const val TAG = "LaunchFullScreenIntentProvider"
+    }
+    private val listeners = ListenerSet<Listener>()
+
+    /**
+     * Registers a listener with this provider. These listeners will be alerted whenever a full
+     * screen intent should be launched for a notification entry.
+     */
+    fun registerListener(listener: Listener) {
+        listeners.addIfAbsent(listener)
+    }
+
+    /** Removes the specified listener. */
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    /**
+     * Sends a request to launch full screen intent for the given notification entry to all
+     * registered listeners.
+     */
+    fun launchFullScreenIntent(entry: NotificationEntry) {
+        if (listeners.isEmpty()) {
+            // This should never happen, but we should definitely know if it does because having
+            // no listeners would indicate that FSIs are getting entirely dropped on the floor.
+            Log.wtf(TAG, "no listeners found when launchFullScreenIntent requested")
+        }
+        for (listener in listeners) {
+            listener.onFullScreenIntentRequested(entry)
+        }
+    }
+
+    /** Listener interface for passing full screen intent launch decisions. */
+    fun interface Listener {
+        /**
+         * Invoked whenever a full screen intent launch is requested for the given notification
+         * entry.
+         */
+        fun onFullScreenIntentRequested(entry: NotificationEntry)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9e7717c..de158c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1341,21 +1341,6 @@
         return mOnKeyguard;
     }
 
-    public void removeAllChildren() {
-        List<ExpandableNotificationRow> notificationChildren =
-                mChildrenContainer.getAttachedChildren();
-        ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
-        for (int i = 0; i < clonedList.size(); i++) {
-            ExpandableNotificationRow row = clonedList.get(i);
-            if (row.keepInParent()) {
-                continue;
-            }
-            mChildrenContainer.removeNotification(row);
-            row.setIsChildInGroup(false, null);
-        }
-        onAttachedChildrenCountChanged();
-    }
-
     @Override
     public void dismiss(boolean refocusOnDismiss) {
         super.dismiss(refocusOnDismiss);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index b2628e4..6f4d6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -719,7 +719,7 @@
      */
     public boolean isBouncerInTransit() {
         return mStatusBarKeyguardViewManager != null
-                && mStatusBarKeyguardViewManager.isBouncerInTransit();
+                && mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 9900e41..9dcbe20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -64,8 +64,10 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -163,7 +165,7 @@
     private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
-    private BiometricModeListener mBiometricModeListener;
+    private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
 
     private final MetricsLogger mMetricsLogger;
     private final AuthController mAuthController;
@@ -305,9 +307,14 @@
         mKeyguardViewController = keyguardViewController;
     }
 
-    /** Sets a {@link BiometricModeListener}. */
-    public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
-        mBiometricModeListener = biometricModeListener;
+    /** Adds a {@link BiometricModeListener}. */
+    public void addBiometricModeListener(BiometricModeListener listener) {
+        mBiometricModeListeners.add(listener);
+    }
+
+    /** Removes a {@link BiometricModeListener}. */
+    public void removeBiometricModeListener(BiometricModeListener listener) {
+        mBiometricModeListeners.remove(listener);
     }
 
     private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -455,7 +462,7 @@
                 break;
             case MODE_SHOW_BOUNCER:
                 Trace.beginSection("MODE_SHOW_BOUNCER");
-                mKeyguardViewController.showBouncer(true);
+                mKeyguardViewController.showPrimaryBouncer(true);
                 Trace.endSection();
                 break;
             case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
@@ -481,15 +488,12 @@
                 break;
         }
         onModeChanged(mMode);
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.notifyBiometricAuthModeChanged();
-        }
         Trace.endSection();
     }
 
     private void onModeChanged(@WakeAndUnlockMode int mode) {
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.onModeChanged(mode);
+        for (BiometricModeListener listener : mBiometricModeListeners) {
+            listener.onModeChanged(mode);
         }
     }
 
@@ -538,7 +542,7 @@
             return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
         }
         if (mKeyguardStateController.isShowing()) {
-            if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
+            if (mKeyguardViewController.primaryBouncerIsOrWillBeShowing() && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
             } else if (unlockingAllowed) {
                 return MODE_UNLOCK_COLLAPSING;
@@ -581,7 +585,7 @@
             return MODE_UNLOCK_COLLAPSING;
         }
         if (mKeyguardStateController.isShowing()) {
-            if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
+            if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
                     || mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
             } else if (unlockingAllowed && (bypass || mAuthController.isUdfpsFingerDown())) {
@@ -696,9 +700,8 @@
         mMode = MODE_NONE;
         mBiometricType = null;
         mNotificationShadeWindowController.setForceDozeBrightness(false);
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.onResetMode();
-            mBiometricModeListener.notifyBiometricAuthModeChanged();
+        for (BiometricModeListener listener : mBiometricModeListeners) {
+            listener.onResetMode();
         }
         mNumConsecutiveFpFailures = 0;
         mLastFpFailureUptimeMillis = 0;
@@ -807,10 +810,8 @@
     /** An interface to interact with the {@link BiometricUnlockController}. */
     public interface BiometricModeListener {
         /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
-        void onResetMode();
+        default void onResetMode() {}
         /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
-        void onModeChanged(@WakeAndUnlockMode int mode);
-        /** Called after processing {@link #onModeChanged(int)}. */
-        void notifyBiometricAuthModeChanged();
+        default void onModeChanged(@WakeAndUnlockMode int mode) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 1961e1d..2359e87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -456,7 +456,11 @@
 
     void setTransitionToFullShadeProgress(float transitionToFullShadeProgress);
 
-    void setBouncerHiddenFraction(float expansion);
+    /**
+     * Sets the amount of progress to the bouncer being fully hidden/visible. 1 means the bouncer
+     * is fully hidden, while 0 means the bouncer is visible.
+     */
+    void setPrimaryBouncerHiddenFraction(float expansion);
 
     @VisibleForTesting
     void updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index d6fadca..41f0520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -38,8 +38,8 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -458,7 +458,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         if (displayId != mDisplayId) {
             return;
@@ -471,7 +471,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index acd3d04..1cd3611 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -928,7 +928,7 @@
         }
         mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
                 result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
-                result.mRequestedVisibilities, result.mPackageName, result.mLetterboxDetails);
+                result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
 
         // StatusBarManagerService has a back up of IME token and it's restored here.
         mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
@@ -1510,11 +1510,12 @@
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
-        mBiometricUnlockController.setBiometricModeListener(
+        mBiometricUnlockController.addBiometricModeListener(
                 new BiometricUnlockController.BiometricModeListener() {
                     @Override
                     public void onResetMode() {
                         setWakeAndUnlocking(false);
+                        notifyBiometricAuthModeChanged();
                     }
 
                     @Override
@@ -1525,11 +1526,7 @@
                             case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
                                 setWakeAndUnlocking(true);
                         }
-                    }
-
-                    @Override
-                    public void notifyBiometricAuthModeChanged() {
-                        CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+                        notifyBiometricAuthModeChanged();
                     }
 
                     private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -2552,12 +2549,6 @@
                         // ordering.
                         mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
                     }
-                } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
-                    // We are not dismissing the shade, but the launch transition is already
-                    // finished,
-                    // so nobody will call readyForKeyguardDone anymore. Post it such that
-                    // keyguardDonePending gets called first.
-                    mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone);
                 }
                 return deferred;
             }
@@ -3311,9 +3302,9 @@
                 // lock screen where users can use the UDFPS affordance to enter the device
                 mStatusBarKeyguardViewManager.reset(true);
             } else if (mState == StatusBarState.KEYGUARD
-                    && !mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()
+                    && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
                     && isKeyguardSecure()) {
-                mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
+                mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
             }
         }
     }
@@ -3795,7 +3786,7 @@
      * is fully hidden, while 0 means the bouncer is visible.
      */
     @Override
-    public void setBouncerHiddenFraction(float expansion) {
+    public void setPrimaryBouncerHiddenFraction(float expansion) {
         mScrimController.setBouncerHiddenFraction(expansion);
     }
 
@@ -3817,7 +3808,7 @@
                 mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
         mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
 
-        if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+        if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) {
             if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
                     || mTransitionToFullShadeProgress > 0f) {
                 mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3828,7 +3819,7 @@
             // Bouncer needs the front scrim when it's on top of an activity,
             // tapping on a notification, editing QS or being dismissed by
             // FLAG_DISMISS_KEYGUARD_ACTIVITY.
-            ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
+            ScrimState state = mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming()
                     ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
             mScrimController.transitionTo(state);
         } else if (launchingAffordanceWithPreview) {
@@ -4137,7 +4128,7 @@
      */
     @Override
     public boolean isBouncerShowingScrimmed() {
-        return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming();
+        return isBouncerShowing() && mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 4897c52..78b28d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -23,6 +23,8 @@
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
 import android.widget.FrameLayout
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconViewController
 import com.android.systemui.R
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
@@ -51,13 +53,20 @@
 
     private var ambientIndicationArea: View? = null
     private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
+    private lateinit var lockIconViewController: LockIconViewController
 
     /** Initializes the view. */
     fun init(
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager,
+        lockIconViewController: LockIconViewController,
     ) {
-        binding = bind(this, viewModel, falsingManager)
+        binding = bind(
+                this,
+                viewModel,
+                falsingManager,
+        )
+        this.lockIconViewController = lockIconViewController
     }
 
     /**
@@ -114,4 +123,29 @@
         }
         return insets
     }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+        findViewById<View>(R.id.ambient_indication_container)?.let {
+            val (ambientLeft, ambientTop) = it.locationOnScreen
+            if (binding.shouldConstrainToTopOfLockIcon()) {
+                //make top of ambient indication view the bottom of the lock icon
+                it.layout(
+                        ambientLeft,
+                        lockIconViewController.bottom.toInt(),
+                        right - ambientLeft,
+                        ambientTop + it.measuredHeight
+                )
+            } else {
+                //make bottom of ambient indication view the top of the lock icon
+                val lockLocationTop = lockIconViewController.top
+                it.layout(
+                        ambientLeft,
+                        lockLocationTop.toInt() - it.measuredHeight,
+                        right - ambientLeft,
+                        lockLocationTop.toInt()
+                )
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index a28fca1..d328472 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -55,13 +55,13 @@
 import javax.inject.Inject;
 
 /**
- * A class which manages the bouncer on the lockscreen.
+ * A class which manages the primary (pin/pattern/password) bouncer on the lockscreen.
  * @deprecated Use KeyguardBouncerRepository
  */
 @Deprecated
 public class KeyguardBouncer {
 
-    private static final String TAG = "KeyguardBouncer";
+    private static final String TAG = "PrimaryKeyguardBouncer";
     static final long BOUNCER_FACE_DELAY = 1200;
     public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
     /**
@@ -78,7 +78,7 @@
     private final FalsingCollector mFalsingCollector;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
     private final Handler mHandler;
-    private final List<BouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
+    private final List<PrimaryBouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
@@ -126,7 +126,7 @@
     private KeyguardBouncer(Context context, ViewMediatorCallback callback,
             ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
-            BouncerExpansionCallback expansionCallback,
+            PrimaryBouncerExpansionCallback expansionCallback,
             KeyguardStateController keyguardStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController keyguardBypassController, @Main Handler handler,
@@ -571,37 +571,37 @@
     }
 
     private void dispatchFullyShown() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onFullyShown();
         }
     }
 
     private void dispatchStartingToHide() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onStartingToHide();
         }
     }
 
     private void dispatchStartingToShow() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onStartingToShow();
         }
     }
 
     private void dispatchFullyHidden() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onFullyHidden();
         }
     }
 
     private void dispatchExpansionChanged() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onExpansionChanged(mExpansion);
         }
     }
 
     private void dispatchVisibilityChanged() {
-        for (BouncerExpansionCallback callback : mExpansionCallbacks) {
+        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
             callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE);
         }
     }
@@ -647,7 +647,7 @@
     /**
      * Adds a callback to listen to bouncer expansion updates.
      */
-    public void addBouncerExpansionCallback(BouncerExpansionCallback callback) {
+    public void addBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
         if (!mExpansionCallbacks.contains(callback)) {
             mExpansionCallbacks.add(callback);
         }
@@ -657,11 +657,14 @@
      * Removes a previously added callback. If the callback was never added, this methood
      * does nothing.
      */
-    public void removeBouncerExpansionCallback(BouncerExpansionCallback callback) {
+    public void removeBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
         mExpansionCallbacks.remove(callback);
     }
 
-    public interface BouncerExpansionCallback {
+    /**
+     * Callback updated when the primary bouncer's show and hide states change.
+     */
+    public interface PrimaryBouncerExpansionCallback {
         /**
          * Invoked when the bouncer expansion reaches {@link KeyguardBouncer#EXPANSION_VISIBLE}.
          * This is NOT called each time the bouncer is shown, but rather only when the fully
@@ -745,7 +748,7 @@
          * Construct a KeyguardBouncer that will exist in the given container.
          */
         public KeyguardBouncer create(ViewGroup container,
-                BouncerExpansionCallback expansionCallback) {
+                PrimaryBouncerExpansionCallback expansionCallback) {
             return new KeyguardBouncer(mContext, mCallback, container,
                     mDismissCallbackRegistry, mFalsingCollector, expansionCallback,
                     mKeyguardStateController, mKeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 6e98c49..eba7fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -22,8 +22,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -144,7 +144,7 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
             if (displayId != mDisplayId) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c189ace..4ee2de1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -141,7 +141,6 @@
     /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */
     public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
     public static final int MAX_STATIC_ICONS = 4;
-    private static final int MAX_DOTS = 1;
 
     private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
@@ -166,8 +165,7 @@
     private IconState mLastVisibleIconState;
     private IconState mFirstVisibleIconState;
     private float mVisualOverflowStart;
-    // Keep track of overflow in range [0, 3]
-    private int mNumDots;
+    private boolean mIsShowingOverflowDot;
     private StatusBarIconView mIsolatedIcon;
     private Rect mIsolatedIconLocation;
     private int[] mAbsolutePosition = new int[2];
@@ -387,8 +385,8 @@
         }
     }
 
-    public boolean hasMaxNumDot() {
-        return mNumDots >= MAX_DOTS;
+    public boolean areIconsOverflowing() {
+        return mIsShowingOverflowDot;
     }
 
     private boolean areAnimationsEnabled(StatusBarIconView icon) {
@@ -494,7 +492,7 @@
                     : 1f;
             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
         }
-        mNumDots = 0;
+        mIsShowingOverflowDot = false;
         if (firstOverflowIndex != -1) {
             translationX = mVisualOverflowStart;
             for (int i = firstOverflowIndex; i < childCount; i++) {
@@ -502,15 +500,14 @@
                 IconState iconState = mIconStates.get(view);
                 int dotWidth = mStaticDotDiameter + mDotPadding;
                 iconState.setXTranslation(translationX);
-                if (mNumDots < MAX_DOTS) {
-                    if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
+                if (!mIsShowingOverflowDot) {
+                    if (iconState.iconAppearAmount < 0.8f) {
                         iconState.visibleState = StatusBarIconView.STATE_ICON;
                     } else {
                         iconState.visibleState = StatusBarIconView.STATE_DOT;
-                        mNumDots++;
+                        mIsShowingOverflowDot = true;
                     }
-                    translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
-                            * iconState.iconAppearAmount;
+                    translationX += dotWidth * iconState.iconAppearAmount;
                     mLastVisibleIconState = iconState;
                 } else {
                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
@@ -618,10 +615,6 @@
         return Math.min(getWidth(), translation);
     }
 
-    private float getMaxOverflowStart() {
-        return getLayoutEnd() - mIconSize;
-    }
-
     public void setChangingViewPositions(boolean changingViewPositions) {
         mChangingViewPositions = changingViewPositions;
     }
@@ -645,25 +638,6 @@
         mSpeedBumpIndex = speedBumpIndex;
     }
 
-    public boolean hasOverflow() {
-        return mNumDots > 0;
-    }
-
-    // Give some extra room for btw notifications if we can
-    public int getNoOverflowExtraPadding() {
-        if (mNumDots != 0) {
-            return 0;
-        }
-
-        int collapsedPadding = mIconSize;
-
-        if (collapsedPadding + getFinalTranslationX() > getWidth()) {
-            collapsedPadding = getWidth() - getFinalTranslationX();
-        }
-
-        return collapsedPadding;
-    }
-
     public int getIconSize() {
         return mIconSize;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index cf3a48c..86e27ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -820,15 +820,7 @@
                 mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
             }
         } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
-            float behindFraction = getInterpolatedFraction();
-            behindFraction = (float) Math.pow(behindFraction, 0.8f);
-
-            mBehindAlpha = behindFraction * mDefaultScrimAlpha;
-            mNotificationsAlpha = mBehindAlpha;
-            if (mClipsQsScrim) {
-                mBehindAlpha = 1;
-                mBehindTint = Color.BLACK;
-            }
+            mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
                 || mState == ScrimState.PULSING) {
             Pair<Integer, Float> result = calculateBackStateForState(mState);
@@ -921,7 +913,7 @@
         if (mQsExpansion > 0) {
             behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion);
             float tintProgress = mQsExpansion;
-            if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
+            if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
                 // this is case of - on lockscreen - going from expanded QS to bouncer.
                 // Because mQsExpansion is already interpolated and transition between tints
                 // is too slow, we want to speed it up and make it more aligned to bouncer
@@ -1104,7 +1096,7 @@
     }
 
     private float getInterpolatedFraction() {
-        if (mStatusBarKeyguardViewManager.isBouncerInTransit()) {
+        if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) {
             return BouncerPanelExpansionCalculator
                     .aboutToShowBouncerProgress(mPanelExpansionFraction);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index b447f0d..52430d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -90,11 +90,14 @@
     AUTH_SCRIMMED_SHADE {
         @Override
         public void prepare(ScrimState previousState) {
-            // notif & behind scrim alpha values are determined by ScrimController#applyState
+            // notif scrim alpha values are determined by ScrimController#applyState
             // based on the shade expansion
 
             mFrontTint = Color.BLACK;
             mFrontAlpha = .66f;
+
+            mBehindTint = Color.BLACK;
+            mBehindAlpha = 1f;
         }
     },
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 86f6ff8..0a0ded2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -53,8 +54,9 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
 import com.android.systemui.util.Assert;
 
 import java.util.ArrayList;
@@ -84,7 +86,18 @@
     /** */
     void setIcon(String slot, StatusBarIcon icon);
     /** */
-    void setSignalIcon(String slot, WifiIconState state);
+    void setWifiIcon(String slot, WifiIconState state);
+
+    /**
+     * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
+     * set up (inflated and added to the view hierarchy).
+     *
+     * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
+     * data pipeline. Icons will automatically keep their state up to date, so we don't have to
+     * worry about funneling state objects through anymore.
+     */
+    void setNewWifiIcon();
+
     /** */
     void setMobileIcons(String slot, List<MobileIconState> states);
 
@@ -151,14 +164,14 @@
                 LinearLayout linearLayout,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
                     location,
                     statusBarPipelineFlags,
-                    wifiViewModel,
+                    wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
@@ -218,7 +231,7 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final WifiViewModel mWifiViewModel;
+            private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
             private final DarkIconDispatcher mDarkIconDispatcher;
@@ -226,12 +239,12 @@
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    WifiViewModel wifiViewModel,
+                    WifiUiAdapter wifiUiAdapter,
                     MobileContextProvider mobileContextProvider,
                     MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModel = wifiViewModel;
+                mWifiUiAdapter = wifiUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
                 mMobileUiAdapter = mobileUiAdapter;
                 mDarkIconDispatcher = darkIconDispatcher;
@@ -242,7 +255,7 @@
                         group,
                         location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModel,
+                        mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider,
                         mDarkIconDispatcher);
@@ -260,14 +273,14 @@
                 ViewGroup group,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             super(group,
                     location,
                     statusBarPipelineFlags,
-                    wifiViewModel,
+                    wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
         }
@@ -302,19 +315,19 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final WifiViewModel mWifiViewModel;
+            private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    WifiViewModel wifiViewModel,
+                    WifiUiAdapter wifiUiAdapter,
                     MobileUiAdapter mobileUiAdapter,
                     MobileContextProvider mobileContextProvider
             ) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModel = wifiViewModel;
+                mWifiUiAdapter = wifiUiAdapter;
                 mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
             }
@@ -324,7 +337,7 @@
                         group,
                         location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModel,
+                        mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider);
             }
@@ -336,10 +349,9 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
-        private final StatusBarLocation mLocation;
         private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-        private final WifiViewModel mWifiViewModel;
         private final MobileContextProvider mMobileContextProvider;
+        private final LocationBasedWifiViewModel mWifiViewModel;
         private final MobileIconsViewModel mMobileIconsViewModel;
 
         protected final Context mContext;
@@ -359,26 +371,33 @@
                 ViewGroup group,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             mGroup = group;
-            mLocation = location;
             mStatusBarPipelineFlags = statusBarPipelineFlags;
-            mWifiViewModel = wifiViewModel;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
 
-            if (statusBarPipelineFlags.useNewMobileIcons()) {
-                // This starts the flow for the new pipeline, and will notify us of changes
+            if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
+                // This starts the flow for the new pipeline, and will notify us of changes if
+                // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
                 mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
                 MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
             } else {
                 mMobileIconsViewModel = null;
             }
+
+            if (statusBarPipelineFlags.runNewWifiIconBackend()) {
+                // This starts the flow for the new pipeline, and will notify us of changes if
+                // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
+                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+            } else {
+                mWifiViewModel = null;
+            }
         }
 
         public boolean isDemoable() {
@@ -429,6 +448,9 @@
                 case TYPE_WIFI:
                     return addWifiIcon(index, slot, holder.getWifiState());
 
+                case TYPE_WIFI_NEW:
+                    return addNewWifiIcon(index, slot);
+
                 case TYPE_MOBILE:
                     return addMobileIcon(index, slot, holder.getMobileState());
 
@@ -450,16 +472,13 @@
 
         @VisibleForTesting
         protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
-            final BaseStatusBarFrameLayout view;
             if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                view = onCreateModernStatusBarWifiView(slot);
-                // When [ModernStatusBarWifiView] is created, it will automatically apply the
-                // correct view state so we don't need to call applyWifiState.
-            } else {
-                StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot);
-                wifiView.applyWifiState(state);
-                view = wifiView;
+                throw new IllegalStateException("Attempting to add a mobile icon while the new "
+                        + "icons are enabled is not supported");
             }
+
+            final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
+            view.applyWifiState(state);
             mGroup.addView(view, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
@@ -468,6 +487,17 @@
             return view;
         }
 
+        protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
+            if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+                throw new IllegalStateException("Attempting to add a wifi icon using the new"
+                        + "pipeline, but the enabled flag is false.");
+            }
+
+            ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
+            mGroup.addView(view, index, onCreateLayoutParams());
+            return view;
+        }
+
         @VisibleForTesting
         protected StatusIconDisplayable addMobileIcon(
                 int index,
@@ -523,8 +553,7 @@
         }
 
         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
-            return ModernStatusBarWifiView.constructAndBind(
-                    mContext, slot, mWifiViewModel, mLocation);
+            return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
         }
 
         private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
@@ -600,7 +629,8 @@
                     onSetMobileIcon(viewIndex, holder.getMobileState());
                     return;
                 case TYPE_MOBILE_NEW:
-                    // Nothing, the icon updates itself now
+                case TYPE_WIFI_NEW:
+                    // Nothing, the new icons update themselves
                     return;
                 default:
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 31e960a..674e574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -195,12 +195,13 @@
         }
     }
 
-    /**
-     * Signal icons need to be handled differently, because they can be
-     * composite views
-     */
     @Override
-    public void setSignalIcon(String slot, WifiIconState state) {
+    public void setWifiIcon(String slot, WifiIconState state) {
+        if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+            Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
+            return;
+        }
+
         if (state == null) {
             removeIcon(slot, 0);
             return;
@@ -216,6 +217,24 @@
         }
     }
 
+
+    @Override
+    public void setNewWifiIcon() {
+        if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+            Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
+            return;
+        }
+
+        String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
+        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
+        if (holder == null) {
+            holder = StatusBarIconHolder.forNewWifiIcon();
+            setIcon(slot, holder);
+        } else {
+            // Don't have to do anything in the new world
+        }
+    }
+
     /**
      * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
      * by subId. Don't worry this definitely makes sense and works.
@@ -225,7 +244,7 @@
     @Override
     public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
         if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+            Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
                     + "icons are enabled");
             return;
         }
@@ -251,10 +270,11 @@
     public void setNewMobileIconSubIds(List<Integer> subIds) {
         if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
             Log.d(TAG, "ignoring new pipeline callback, "
-                    + "since the new icons are disabled");
+                    + "since the new mobile icons are disabled");
             return;
         }
-        Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+        String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
+        Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
 
         Collections.reverse(subIds);
 
@@ -262,7 +282,7 @@
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
-                setIcon("mobile", holder);
+                setIcon(slotName, holder);
             } else {
                 // Don't have to do anything in the new world
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 68a203e..f6c0da8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -51,11 +51,24 @@
     @Deprecated
     public static final int TYPE_MOBILE_NEW = 3;
 
+    /**
+     * TODO (b/238425913): address this once the new pipeline is in place
+     * This type exists so that the new wifi pipeline can be used to inform the old view system
+     * about the existence of the wifi icon. The design of the new pipeline should allow for removal
+     * of this icon holder type, and obsolete the need for this entire class.
+     *
+     * @deprecated This field only exists so the new status bar pipeline can interface with the
+     * view holder system.
+     */
+    @Deprecated
+    public static final int TYPE_WIFI_NEW = 4;
+
     @IntDef({
             TYPE_ICON,
             TYPE_WIFI,
             TYPE_MOBILE,
-            TYPE_MOBILE_NEW
+            TYPE_MOBILE_NEW,
+            TYPE_WIFI_NEW
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface IconType {}
@@ -95,6 +108,13 @@
         return holder;
     }
 
+    /** Creates a new holder with for the new wifi icon. */
+    public static StatusBarIconHolder forNewWifiIcon() {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mType = TYPE_WIFI_NEW;
+        return holder;
+    }
+
     /** */
     public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -172,9 +192,10 @@
             case TYPE_MOBILE:
                 return mMobileState.visible;
             case TYPE_MOBILE_NEW:
-                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+            case TYPE_WIFI_NEW:
+                // The new pipeline controls visibilities via the view model and view binder, so
+                // this is effectively an unused return value.
                 return true;
-
             default:
                 return true;
         }
@@ -199,7 +220,9 @@
                 break;
 
             case TYPE_MOBILE_NEW:
-                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+            case TYPE_WIFI_NEW:
+                // The new pipeline controls visibilities via the view model and view binder, so
+                // ignore setVisible.
                 break;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 93b6437..e9d4e55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -60,8 +60,8 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -78,7 +78,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.FoldAodAnimationController;
@@ -135,62 +135,63 @@
     @Nullable
     private final FoldAodAnimationController mFoldAodAnimationController;
     private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
-    private final BouncerCallbackInteractor mBouncerCallbackInteractor;
-    private final BouncerInteractor mBouncerInteractor;
-    private final BouncerView mBouncerView;
+    private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    private final BouncerView mPrimaryBouncerView;
     private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
 
-    private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
-        private boolean mBouncerAnimating;
+    private final PrimaryBouncerExpansionCallback mExpansionCallback =
+            new PrimaryBouncerExpansionCallback() {
+            private boolean mPrimaryBouncerAnimating;
 
-        @Override
-        public void onFullyShown() {
-            mBouncerAnimating = false;
-            updateStates();
-        }
-
-        @Override
-        public void onStartingToHide() {
-            mBouncerAnimating = true;
-            updateStates();
-        }
-
-        @Override
-        public void onStartingToShow() {
-            mBouncerAnimating = true;
-            updateStates();
-        }
-
-        @Override
-        public void onFullyHidden() {
-            mBouncerAnimating = false;
-        }
-
-        @Override
-        public void onExpansionChanged(float expansion) {
-            if (mBouncerAnimating) {
-                mCentralSurfaces.setBouncerHiddenFraction(expansion);
-            }
-        }
-
-        @Override
-        public void onVisibilityChanged(boolean isVisible) {
-            mCentralSurfaces
-                    .setBouncerShowingOverDream(
-                            isVisible && mDreamOverlayStateController.isOverlayActive());
-
-            if (!isVisible) {
-                mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
+            @Override
+            public void onFullyShown() {
+                mPrimaryBouncerAnimating = false;
+                updateStates();
             }
 
-            /* Register predictive back callback when keyguard becomes visible, and unregister
-            when it's hidden. */
-            if (isVisible) {
-                registerBackCallback();
-            } else {
-                unregisterBackCallback();
+            @Override
+            public void onStartingToHide() {
+                mPrimaryBouncerAnimating = true;
+                updateStates();
             }
-        }
+
+            @Override
+            public void onStartingToShow() {
+                mPrimaryBouncerAnimating = true;
+                updateStates();
+            }
+
+            @Override
+            public void onFullyHidden() {
+                mPrimaryBouncerAnimating = false;
+            }
+
+            @Override
+            public void onExpansionChanged(float expansion) {
+                if (mPrimaryBouncerAnimating) {
+                    mCentralSurfaces.setPrimaryBouncerHiddenFraction(expansion);
+                }
+            }
+
+            @Override
+            public void onVisibilityChanged(boolean isVisible) {
+                mCentralSurfaces.setBouncerShowingOverDream(
+                        isVisible && mDreamOverlayStateController.isOverlayActive());
+
+                if (!isVisible) {
+                    mCentralSurfaces.setPrimaryBouncerHiddenFraction(
+                            KeyguardBouncer.EXPANSION_HIDDEN);
+                }
+
+                /* Register predictive back callback when keyguard becomes visible, and unregister
+                when it's hidden. */
+                if (isVisible) {
+                    registerBackCallback();
+                } else {
+                    unregisterBackCallback();
+                }
+            }
     };
 
     private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
@@ -223,7 +224,7 @@
 
     private View mNotificationContainer;
 
-    @Nullable protected KeyguardBouncer mBouncer;
+    @Nullable protected KeyguardBouncer mPrimaryBouncer;
     protected boolean mRemoteInputActive;
     private boolean mGlobalActionsVisible = false;
     private boolean mLastGlobalActionsVisible = false;
@@ -236,8 +237,8 @@
     protected boolean mFirstUpdate = true;
     protected boolean mLastShowing;
     protected boolean mLastOccluded;
-    private boolean mLastBouncerShowing;
-    private boolean mLastBouncerIsOrWillBeShowing;
+    private boolean mLastPrimaryBouncerShowing;
+    private boolean mLastPrimaryBouncerIsOrWillBeShowing;
     private boolean mLastBouncerDismissible;
     protected boolean mLastRemoteInputActive;
     private boolean mLastDozing;
@@ -265,7 +266,7 @@
     private final LatencyTracker mLatencyTracker;
     private final KeyguardSecurityModel mKeyguardSecurityModel;
     private KeyguardBypassController mBypassController;
-    @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
+    @Nullable private AlternateBouncer mAlternateBouncer;
 
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -300,9 +301,9 @@
             LatencyTracker latencyTracker,
             KeyguardSecurityModel keyguardSecurityModel,
             FeatureFlags featureFlags,
-            BouncerCallbackInteractor bouncerCallbackInteractor,
-            BouncerInteractor bouncerInteractor,
-            BouncerView bouncerView) {
+            PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
+            PrimaryBouncerInteractor primaryBouncerInteractor,
+            BouncerView primaryBouncerView) {
         mContext = context;
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
@@ -320,9 +321,9 @@
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
         mKeyguardSecurityModel = keyguardSecurityModel;
-        mBouncerCallbackInteractor = bouncerCallbackInteractor;
-        mBouncerInteractor = bouncerInteractor;
-        mBouncerView = bouncerView;
+        mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
+        mPrimaryBouncerView = primaryBouncerView;
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
         mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
@@ -340,9 +341,9 @@
 
         ViewGroup container = mCentralSurfaces.getBouncerContainer();
         if (mIsModernBouncerEnabled) {
-            mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
+            mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
         } else {
-            mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
+            mPrimaryBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
         }
         mNotificationPanelViewController = notificationPanelViewController;
         if (shadeExpansionStateManager != null) {
@@ -361,20 +362,20 @@
      * Sets the given alt auth interceptor to null if it's the current auth interceptor. Else,
      * does nothing.
      */
-    public void removeAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
-        if (Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
-            mAlternateAuthInterceptor = null;
-            resetAlternateAuth(true);
+    public void removeAlternateAuthInterceptor(@NonNull AlternateBouncer authInterceptor) {
+        if (Objects.equals(mAlternateBouncer, authInterceptor)) {
+            mAlternateBouncer = null;
+            hideAlternateBouncer(true);
         }
     }
 
     /**
      * Sets a new alt auth interceptor.
      */
-    public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
-        if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
-            mAlternateAuthInterceptor = authInterceptor;
-            resetAlternateAuth(false);
+    public void setAlternateBouncer(@NonNull AlternateBouncer authInterceptor) {
+        if (!Objects.equals(mAlternateBouncer, authInterceptor)) {
+            mAlternateBouncer = authInterceptor;
+            hideAlternateBouncer(false);
         }
     }
 
@@ -458,49 +459,48 @@
         if (mDozing && !mPulsing) {
             return;
         } else if (mNotificationPanelViewController.isUnlockHintRunning()) {
-            if (mBouncer != null) {
-                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
         } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
             // CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
             return;
-        } else if (bouncerNeedsScrimming()) {
-            if (mBouncer != null) {
-                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+        } else if (primaryBouncerNeedsScrimming()) {
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             } else {
-                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+                mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             }
         } else if (mKeyguardStateController.isShowing()  && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
-                    && !mNotificationPanelViewController.isLaunchTransitionFinished()
                     && !isUnlockCollapsing()) {
-                if (mBouncer != null) {
-                    mBouncer.setExpansion(fraction);
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.setExpansion(fraction);
                 } else {
-                    mBouncerInteractor.setPanelExpansion(fraction);
+                    mPrimaryBouncerInteractor.setPanelExpansion(fraction);
                 }
             }
             if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
                     && !mKeyguardStateController.canDismissLockScreen()
-                    && !bouncerIsShowing()
+                    && !primaryBouncerIsShowing()
                     && !bouncerIsAnimatingAway()) {
-                if (mBouncer != null) {
-                    mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
                 } else {
-                    mBouncerInteractor.show(/* isScrimmed= */false);
+                    mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
                 }
             }
-        } else if (!mKeyguardStateController.isShowing()  && isBouncerInTransit()) {
+        } else if (!mKeyguardStateController.isShowing()  && isPrimaryBouncerInTransit()) {
             // Keyguard is not visible anymore, but expansion animation was still running.
             // We need to hide the bouncer, otherwise it will be stuck in transit.
-            if (mBouncer != null) {
-                mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
         } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
             // Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -544,17 +544,17 @@
         if (needsFullscreenBouncer() && !mDozing) {
             // The keyguard might be showing (already). So we need to hide it.
             mCentralSurfaces.hideKeyguard();
-            if (mBouncer != null) {
-                mBouncer.show(true /* resetSecuritySelection */);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.show(true /* resetSecuritySelection */);
             } else {
-                mBouncerInteractor.show(true);
+                mPrimaryBouncerInteractor.show(true);
             }
         } else {
             mCentralSurfaces.showKeyguard();
             if (hideBouncerWhenShowing) {
                 hideBouncer(false /* destroyView */);
-                if (mBouncer != null) {
-                    mBouncer.prepare();
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.prepare();
                 }
             }
         }
@@ -562,23 +562,25 @@
     }
 
     /**
-     * If applicable, shows the alternate authentication bouncer. Else, shows the input
-     * (pin/password/pattern) bouncer.
-     * @param scrimmed true when the input bouncer should show scrimmed, false when the user will be
-     * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
+     *
+     * If possible, shows the alternate bouncer. Else, shows the primary (pin/pattern/password)
+     * bouncer.
+     * @param scrimmed true when the primary bouncer should show scrimmed,
+     *                 false when the user will be dragging it and translation should be deferred
+     *                 {@see KeyguardBouncer#show(boolean, boolean)}
      */
-    public void showGenericBouncer(boolean scrimmed) {
-        if (shouldShowAltAuth()) {
-            updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
+    public void showBouncer(boolean scrimmed) {
+        if (canShowAlternateBouncer()) {
+            updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
             return;
         }
 
-        showBouncer(scrimmed);
+        showPrimaryBouncer(scrimmed);
     }
 
-    /** Whether we should show the alternate authentication instead of the traditional bouncer. */
-    public boolean shouldShowAltAuth() {
-        return mAlternateAuthInterceptor != null
+    /** Whether we can show the alternate bouncer instead of the primary bouncer. */
+    public boolean canShowAlternateBouncer() {
+        return mAlternateBouncer != null
                 && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
     }
 
@@ -587,10 +589,10 @@
      */
     @VisibleForTesting
     void hideBouncer(boolean destroyView) {
-        if (mBouncer != null) {
-            mBouncer.hide(destroyView);
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.hide(destroyView);
         } else {
-            mBouncerInteractor.hide();
+            mPrimaryBouncerInteractor.hide();
         }
         if (mKeyguardStateController.isShowing()) {
             // If we were showing the bouncer and then aborting, we need to also clear out any
@@ -601,19 +603,19 @@
     }
 
     /**
-     * Shows the keyguard input bouncer - the password challenge on the lock screen
+     * Shows the primary bouncer - the pin/pattern/password challenge on the lock screen.
      *
      * @param scrimmed true when the bouncer should show scrimmed, false when the user will be
      * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
      */
-    public void showBouncer(boolean scrimmed) {
-        resetAlternateAuth(false);
+    public void showPrimaryBouncer(boolean scrimmed) {
+        hideAlternateBouncer(false);
 
         if (mKeyguardStateController.isShowing()  && !isBouncerShowing()) {
-            if (mBouncer != null) {
-                mBouncer.show(false /* resetSecuritySelection */, scrimmed);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.show(false /* resetSecuritySelection */, scrimmed);
             } else {
-                mBouncerInteractor.show(scrimmed);
+                mPrimaryBouncerInteractor.show(scrimmed);
             }
         }
         updateStates();
@@ -645,42 +647,41 @@
 
                 // If there is an an alternate auth interceptor (like the UDFPS), show that one
                 // instead of the bouncer.
-                if (shouldShowAltAuth()) {
+                if (canShowAlternateBouncer()) {
                     if (!afterKeyguardGone) {
-                        if (mBouncer != null) {
-                            mBouncer.setDismissAction(mAfterKeyguardGoneAction,
+                        if (mPrimaryBouncer != null) {
+                            mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction,
                                     mKeyguardGoneCancelAction);
                         } else {
-                            mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+                            mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
                                     mKeyguardGoneCancelAction);
                         }
                         mAfterKeyguardGoneAction = null;
                         mKeyguardGoneCancelAction = null;
                     }
 
-                    updateAlternateAuthShowing(
-                            mAlternateAuthInterceptor.showAlternateAuthBouncer());
+                    updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer());
                     return;
                 }
 
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    if (mBouncer != null) {
-                        mBouncer.show(false /* resetSecuritySelection */);
+                    if (mPrimaryBouncer != null) {
+                        mPrimaryBouncer.show(false /* resetSecuritySelection */);
                     } else {
-                        mBouncerInteractor.show(/* isScrimmed= */true);
+                        mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
                     }
                 } else {
                     // after authentication success, run dismiss action with the option to defer
                     // hiding the keyguard based on the return value of the OnDismissAction
-                    if (mBouncer != null) {
-                        mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
+                    if (mPrimaryBouncer != null) {
+                        mPrimaryBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
                                 mKeyguardGoneCancelAction);
                     } else {
-                        mBouncerInteractor.setDismissAction(
+                        mPrimaryBouncerInteractor.setDismissAction(
                                 mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
-                        mBouncerInteractor.show(/* isScrimmed= */true);
+                        mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
                     }
                     // bouncer will handle the dismiss action, so we no longer need to track it here
                     mAfterKeyguardGoneAction = null;
@@ -725,28 +726,28 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing);
             }
-            resetAlternateAuth(false);
+            hideAlternateBouncer(false);
             mKeyguardUpdateManager.sendKeyguardReset();
             updateStates();
         }
     }
 
     @Override
-    public void resetAlternateAuth(boolean forceUpdateScrim) {
-        final boolean updateScrim = (mAlternateAuthInterceptor != null
-                && mAlternateAuthInterceptor.hideAlternateAuthBouncer())
+    public void hideAlternateBouncer(boolean forceUpdateScrim) {
+        final boolean updateScrim = (mAlternateBouncer != null
+                && mAlternateBouncer.hideAlternateBouncer())
                 || forceUpdateScrim;
-        updateAlternateAuthShowing(updateScrim);
+        updateAlternateBouncerShowing(updateScrim);
     }
 
-    private void updateAlternateAuthShowing(boolean updateScrim) {
-        final boolean isShowingAltAuth = isShowingAlternateAuth();
+    private void updateAlternateBouncerShowing(boolean updateScrim) {
+        final boolean isShowingAlternateBouncer = isShowingAlternateBouncer();
         if (mKeyguardMessageAreaController != null) {
-            mKeyguardMessageAreaController.setIsVisible(isShowingAltAuth);
+            mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
             mKeyguardMessageAreaController.setMessage("");
         }
-        mBypassController.setAltBouncerShowing(isShowingAltAuth);
-        mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAltAuth);
+        mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
+        mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAlternateBouncer);
 
         if (updateScrim) {
             mCentralSurfaces.updateScrimController();
@@ -783,10 +784,10 @@
 
     @Override
     public void onFinishedGoingToSleep() {
-        if (mBouncer != null) {
-            mBouncer.onScreenTurnedOff();
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.onScreenTurnedOff();
         } else {
-            mBouncerInteractor.onScreenTurnedOff();
+            mPrimaryBouncerInteractor.onScreenTurnedOff();
         }
     }
 
@@ -845,21 +846,6 @@
         if (isShowing && isOccluding) {
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
-            if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
-                final Runnable endRunnable = new Runnable() {
-                    @Override
-                    public void run() {
-                        mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
-                        reset(true /* hideBouncerWhenShowing */);
-                    }
-                };
-                mCentralSurfaces.fadeKeyguardAfterLaunchTransition(
-                        null /* beforeFading */,
-                        endRunnable,
-                        endRunnable);
-                return;
-            }
-
             if (mCentralSurfaces.isLaunchingActivityOverLockscreen()) {
                 // When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
                 // collapse runnables will be run.
@@ -886,18 +872,18 @@
             // by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
             reset(isOccluding /* hideBouncerWhenShowing*/);
         }
-        if (animate && !isOccluded && isShowing && !bouncerIsShowing()) {
+        if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
             mCentralSurfaces.animateKeyguardUnoccluding();
         }
     }
 
     @Override
     public void startPreHideAnimation(Runnable finishRunnable) {
-        if (bouncerIsShowing()) {
-            if (mBouncer != null) {
-                mBouncer.startPreHideAnimation(finishRunnable);
+        if (primaryBouncerIsShowing()) {
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.startPreHideAnimation(finishRunnable);
             } else {
-                mBouncerInteractor.startDisappearAnimation(finishRunnable);
+                mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
             }
             mNotificationPanelViewController.startBouncerPreHideAnimation();
 
@@ -931,8 +917,7 @@
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
 
-        if (mNotificationPanelViewController.isLaunchTransitionFinished()
-                || mKeyguardStateController.isFlingingToDismissKeyguard()) {
+        if (mKeyguardStateController.isFlingingToDismissKeyguard()) {
             final boolean wasFlingingToDismissKeyguard =
                     mKeyguardStateController.isFlingingToDismissKeyguard();
             mCentralSurfaces.fadeKeyguardAfterLaunchTransition(new Runnable() {
@@ -1011,13 +996,13 @@
             updateResources();
             return;
         }
-        boolean wasShowing = bouncerIsShowing();
-        boolean wasScrimmed = bouncerIsScrimmed();
+        boolean wasShowing = primaryBouncerIsShowing();
+        boolean wasScrimmed = primaryBouncerIsScrimmed();
 
         hideBouncer(true /* destroyView */);
-        mBouncer.prepare();
+        mPrimaryBouncer.prepare();
 
-        if (wasShowing) showBouncer(wasScrimmed);
+        if (wasShowing) showPrimaryBouncer(wasScrimmed);
     }
 
     public void onKeyguardFadedAway() {
@@ -1061,8 +1046,8 @@
      * WARNING: This method might cause Binder calls.
      */
     public boolean isSecure() {
-        if (mBouncer != null) {
-            return mBouncer.isSecure();
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isSecure();
         }
 
         return mKeyguardSecurityModel.getSecurityMode(
@@ -1076,7 +1061,7 @@
      * @return whether a back press can be handled right now.
      */
     public boolean canHandleBackPressed() {
-        return bouncerIsShowing();
+        return primaryBouncerIsShowing();
     }
 
     /**
@@ -1089,7 +1074,7 @@
 
         mCentralSurfaces.endAffordanceLaunch();
         // The second condition is for SIM card locked bouncer
-        if (bouncerIsScrimmed() && !needsFullscreenBouncer()) {
+        if (primaryBouncerIsScrimmed() && !needsFullscreenBouncer()) {
             hideBouncer(false);
             updateStates();
         } else {
@@ -1110,27 +1095,27 @@
 
     @Override
     public boolean isBouncerShowing() {
-        return bouncerIsShowing() || isShowingAlternateAuth();
+        return primaryBouncerIsShowing() || isShowingAlternateBouncer();
     }
 
     @Override
-    public boolean bouncerIsOrWillBeShowing() {
-        return isBouncerShowing() || isBouncerInTransit();
+    public boolean primaryBouncerIsOrWillBeShowing() {
+        return isBouncerShowing() || isPrimaryBouncerInTransit();
     }
 
     public boolean isFullscreenBouncer() {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().isFullScreenBouncer();
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
         }
-        return mBouncer != null && mBouncer.isFullscreenBouncer();
+        return mPrimaryBouncer != null && mPrimaryBouncer.isFullscreenBouncer();
     }
 
     /**
      * Clear out any potential actions that were saved to run when the device is unlocked
      */
     public void cancelPostAuthActions() {
-        if (bouncerIsOrWillBeShowing()) {
-            return; // allow bouncer to trigger saved actions
+        if (primaryBouncerIsOrWillBeShowing()) {
+            return; // allow the primary bouncer to trigger saved actions
         }
         mAfterKeyguardGoneAction = null;
         mDismissActionWillAnimateOnKeyguard = false;
@@ -1169,25 +1154,25 @@
         }
         boolean showing = mKeyguardStateController.isShowing();
         boolean occluded = mKeyguardStateController.isOccluded();
-        boolean bouncerShowing = bouncerIsShowing();
-        boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
-        boolean bouncerDismissible = !isFullscreenBouncer();
+        boolean primaryBouncerShowing = primaryBouncerIsShowing();
+        boolean primaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing();
+        boolean primaryBouncerDismissible = !isFullscreenBouncer();
         boolean remoteInputActive = mRemoteInputActive;
 
-        if ((bouncerDismissible || !showing || remoteInputActive) !=
-                (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
+        if ((primaryBouncerDismissible || !showing || remoteInputActive)
+                != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
                 || mFirstUpdate) {
-            if (bouncerDismissible || !showing || remoteInputActive) {
-                if (mBouncer != null) {
-                    mBouncer.setBackButtonEnabled(true);
+            if (primaryBouncerDismissible || !showing || remoteInputActive) {
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.setBackButtonEnabled(true);
                 } else {
-                    mBouncerInteractor.setBackButtonEnabled(true);
+                    mPrimaryBouncerInteractor.setBackButtonEnabled(true);
                 }
             } else {
-                if (mBouncer != null) {
-                    mBouncer.setBackButtonEnabled(false);
+                if (mPrimaryBouncer != null) {
+                    mPrimaryBouncer.setBackButtonEnabled(false);
                 } else {
-                    mBouncerInteractor.setBackButtonEnabled(false);
+                    mPrimaryBouncerInteractor.setBackButtonEnabled(false);
                 }
             }
         }
@@ -1198,23 +1183,23 @@
             updateNavigationBarVisibility(navBarVisible);
         }
 
-        if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
-            mNotificationShadeWindowController.setBouncerShowing(bouncerShowing);
-            mCentralSurfaces.setBouncerShowing(bouncerShowing);
+        if (primaryBouncerShowing != mLastPrimaryBouncerShowing || mFirstUpdate) {
+            mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
+            mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
         }
-        if (bouncerIsOrWillBeShowing != mLastBouncerIsOrWillBeShowing || mFirstUpdate
-                || bouncerShowing != mLastBouncerShowing) {
-            mKeyguardUpdateManager.sendKeyguardBouncerChanged(bouncerIsOrWillBeShowing,
-                    bouncerShowing);
+        if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
+                || primaryBouncerShowing != mLastPrimaryBouncerShowing) {
+            mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+                    primaryBouncerShowing);
         }
 
         mFirstUpdate = false;
         mLastShowing = showing;
         mLastGlobalActionsVisible = mGlobalActionsVisible;
         mLastOccluded = occluded;
-        mLastBouncerShowing = bouncerShowing;
-        mLastBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing;
-        mLastBouncerDismissible = bouncerDismissible;
+        mLastPrimaryBouncerShowing = primaryBouncerShowing;
+        mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing;
+        mLastBouncerDismissible = primaryBouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
         mLastDozing = mDozing;
         mLastPulsing = mPulsing;
@@ -1258,7 +1243,7 @@
                 || mPulsing && !mIsDocked)
                 && mGesturalNav;
         return (!keyguardVisible && !hideWhileDozing && !mScreenOffAnimationPlaying
-                || bouncerIsShowing()
+                || primaryBouncerIsShowing()
                 || mRemoteInputActive
                 || keyguardWithGestureNav
                 || mGlobalActionsVisible);
@@ -1274,32 +1259,32 @@
                 && !mLastScreenOffAnimationPlaying || mLastPulsing && !mLastIsDocked)
                 && mLastGesturalNav;
         return (!keyguardShowing && !hideWhileDozing && !mLastScreenOffAnimationPlaying
-                || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
+                || mLastPrimaryBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
                 || mLastGlobalActionsVisible);
     }
 
     public boolean shouldDismissOnMenuPressed() {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().shouldDismissOnMenuPressed();
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
         }
-        return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
+        return mPrimaryBouncer != null && mPrimaryBouncer.shouldDismissOnMenuPressed();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().interceptMediaKey(event);
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
         }
-        return mBouncer != null && mBouncer.interceptMediaKey(event);
+        return mPrimaryBouncer != null && mPrimaryBouncer.interceptMediaKey(event);
     }
 
     /**
      * @return true if the pre IME back event should be handled
      */
     public boolean dispatchBackKeyEventPreIme() {
-        if (mBouncerView.getDelegate() != null) {
-            return mBouncerView.getDelegate().dispatchBackKeyEventPreIme();
+        if (mPrimaryBouncerView.getDelegate() != null) {
+            return mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
         }
-        return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
+        return mPrimaryBouncer != null && mPrimaryBouncer.dispatchBackKeyEventPreIme();
     }
 
     public void readyForKeyguardDone() {
@@ -1308,7 +1293,7 @@
 
     @Override
     public boolean shouldDisableWindowAnimationsForUnlock() {
-        return mNotificationPanelViewController.isLaunchTransitionFinished();
+        return false;
     }
 
     @Override
@@ -1345,29 +1330,29 @@
      * fingerprint.
      */
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
-        if (mBouncer != null) {
-            mBouncer.notifyKeyguardAuthenticated(strongAuth);
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.notifyKeyguardAuthenticated(strongAuth);
         } else {
-            mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
+            mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
         }
 
-        if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
-            resetAlternateAuth(false);
+        if (mAlternateBouncer != null && isShowingAlternateBouncer()) {
+            hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
     public void setKeyguardMessage(String message, ColorStateList colorState) {
-        if (isShowingAlternateAuth()) {
+        if (isShowingAlternateBouncer()) {
             if (mKeyguardMessageAreaController != null) {
                 mKeyguardMessageAreaController.setMessage(message);
             }
         } else {
-            if (mBouncer != null) {
-                mBouncer.showMessage(message, colorState);
+            if (mPrimaryBouncer != null) {
+                mPrimaryBouncer.showMessage(message, colorState);
             } else {
-                mBouncerInteractor.showMessage(message, colorState);
+                mPrimaryBouncerInteractor.showMessage(message, colorState);
             }
         }
     }
@@ -1406,12 +1391,15 @@
         }
     }
 
-    public boolean bouncerNeedsScrimming() {
+    /**
+     * Whether the primary bouncer requires scrimming.
+     */
+    public boolean primaryBouncerNeedsScrimming() {
         // When a dream overlay is active, scrimming will cause any expansion to immediately expand.
         return (mKeyguardStateController.isOccluded()
                 && !mDreamOverlayStateController.isOverlayActive())
-                || bouncerWillDismissWithAction()
-                || (bouncerIsShowing() && bouncerIsScrimmed())
+                || primaryBouncerWillDismissWithAction()
+                || (primaryBouncerIsShowing() && primaryBouncerIsScrimmed())
                 || isFullscreenBouncer();
     }
 
@@ -1421,10 +1409,10 @@
      * configuration.
      */
     public void updateResources() {
-        if (mBouncer != null) {
-            mBouncer.updateResources();
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.updateResources();
         } else {
-            mBouncerInteractor.updateResources();
+            mPrimaryBouncerInteractor.updateResources();
         }
     }
 
@@ -1436,19 +1424,20 @@
         pw.println("  mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables);
         pw.println("  mPendingWakeupAction: " + mPendingWakeupAction);
         pw.println("  isBouncerShowing(): " + isBouncerShowing());
-        pw.println("  bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+        pw.println("  bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
         pw.println("  Registered KeyguardViewManagerCallbacks:");
         for (KeyguardViewManagerCallback callback : mCallbacks) {
             pw.println("      " + callback);
         }
 
-        if (mBouncer != null) {
-            mBouncer.dump(pw);
+        if (mPrimaryBouncer != null) {
+            pw.println("PrimaryBouncer:");
+            mPrimaryBouncer.dump(pw);
         }
 
-        if (mAlternateAuthInterceptor != null) {
-            pw.println("AltAuthInterceptor: ");
-            mAlternateAuthInterceptor.dump(pw);
+        if (mAlternateBouncer != null) {
+            pw.println("AlternateBouncer:");
+            mAlternateBouncer.dump(pw);
         }
     }
 
@@ -1496,13 +1485,12 @@
     }
 
     @Nullable
-    public KeyguardBouncer getBouncer() {
-        return mBouncer;
+    public KeyguardBouncer getPrimaryBouncer() {
+        return mPrimaryBouncer;
     }
 
-    public boolean isShowingAlternateAuth() {
-        return mAlternateAuthInterceptor != null
-                && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
+    public boolean isShowingAlternateBouncer() {
+        return mAlternateBouncer != null && mAlternateBouncer.isShowingAlternateBouncer();
     }
 
     /**
@@ -1516,10 +1504,10 @@
 
     /** Update keyguard position based on a tapped X coordinate. */
     public void updateKeyguardPosition(float x) {
-        if (mBouncer != null) {
-            mBouncer.updateKeyguardPosition(x);
+        if (mPrimaryBouncer != null) {
+            mPrimaryBouncer.updateKeyguardPosition(x);
         } else {
-            mBouncerInteractor.setKeyguardPosition(x);
+            mPrimaryBouncerInteractor.setKeyguardPosition(x);
         }
     }
 
@@ -1551,41 +1539,41 @@
      */
     public void requestFp(boolean request, int udfpsColor) {
         mKeyguardUpdateManager.requestFingerprintAuthOnOccludingApp(request);
-        if (mAlternateAuthInterceptor != null) {
-            mAlternateAuthInterceptor.requestUdfps(request, udfpsColor);
+        if (mAlternateBouncer != null) {
+            mAlternateBouncer.requestUdfps(request, udfpsColor);
         }
     }
 
     /**
      * Returns if bouncer expansion is between 0 and 1 non-inclusive.
      */
-    public boolean isBouncerInTransit() {
-        if (mBouncer != null) {
-            return mBouncer.inTransit();
+    public boolean isPrimaryBouncerInTransit() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.inTransit();
         } else {
-            return mBouncerInteractor.isInTransit();
+            return mPrimaryBouncerInteractor.isInTransit();
         }
     }
 
     /**
      * Returns if bouncer is showing
      */
-    public boolean bouncerIsShowing() {
-        if (mBouncer != null) {
-            return mBouncer.isShowing();
+    public boolean primaryBouncerIsShowing() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isShowing();
         } else {
-            return mBouncerInteractor.isFullyShowing();
+            return mPrimaryBouncerInteractor.isFullyShowing();
         }
     }
 
     /**
      * Returns if bouncer is scrimmed
      */
-    public boolean bouncerIsScrimmed() {
-        if (mBouncer != null) {
-            return mBouncer.isScrimmed();
+    public boolean primaryBouncerIsScrimmed() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isScrimmed();
         } else {
-            return mBouncerInteractor.isScrimmed();
+            return mPrimaryBouncerInteractor.isScrimmed();
         }
     }
 
@@ -1593,10 +1581,10 @@
      * Returns if bouncer is animating away
      */
     public boolean bouncerIsAnimatingAway() {
-        if (mBouncer != null) {
-            return mBouncer.isAnimatingAway();
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.isAnimatingAway();
         } else {
-            return mBouncerInteractor.isAnimatingAway();
+            return mPrimaryBouncerInteractor.isAnimatingAway();
         }
 
     }
@@ -1604,11 +1592,11 @@
     /**
      * Returns if bouncer will dismiss with action
      */
-    public boolean bouncerWillDismissWithAction() {
-        if (mBouncer != null) {
-            return mBouncer.willDismissWithAction();
+    public boolean primaryBouncerWillDismissWithAction() {
+        if (mPrimaryBouncer != null) {
+            return mPrimaryBouncer.willDismissWithAction();
         } else {
-            return mBouncerInteractor.willDismissWithAction();
+            return mPrimaryBouncerInteractor.willDismissWithAction();
         }
     }
 
@@ -1623,26 +1611,26 @@
     }
 
     /**
-     * Delegate used to send show/reset events to an alternate authentication method instead of the
-     * regular pin/pattern/password bouncer.
+     * Delegate used to send show and hide events to an alternate authentication method instead of
+     * the regular pin/pattern/password bouncer.
      */
-    public interface AlternateAuthInterceptor {
+    public interface AlternateBouncer {
         /**
          * Show alternate authentication bouncer.
          * @return whether alternate auth method was newly shown
          */
-        boolean showAlternateAuthBouncer();
+        boolean showAlternateBouncer();
 
         /**
          * Hide alternate authentication bouncer
          * @return whether the alternate auth method was newly hidden
          */
-        boolean hideAlternateAuthBouncer();
+        boolean hideAlternateBouncer();
 
         /**
          * @return true if the alternate auth bouncer is showing
          */
-        boolean isShowingAlternateAuthBouncer();
+        boolean isShowingAlternateBouncer();
 
         /**
          * Use when an app occluding the keyguard would like to give the user ability to
@@ -1654,7 +1642,7 @@
         void requestUdfps(boolean requestUdfps, int color);
 
         /**
-         * print information for the alternate auth interceptor registered
+         * print information for the alternate bouncer registered
          */
         void dump(PrintWriter pw);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 5cd2ba1..b6ae4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -23,7 +23,6 @@
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.TaskStackBuilder;
 import android.content.Context;
@@ -60,9 +59,8 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -126,7 +124,6 @@
             Context context,
             Handler mainThreadHandler,
             Executor uiBgExecutor,
-            NotifPipeline notifPipeline,
             NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             ActivityStarter activityStarter,
@@ -151,7 +148,8 @@
             NotificationPresenter presenter,
             NotificationPanelViewController panel,
             ActivityLaunchAnimator activityLaunchAnimator,
-            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
+            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
+            LaunchFullScreenIntentProvider launchFullScreenIntentProvider) {
         mContext = context;
         mMainThreadHandler = mainThreadHandler;
         mUiBgExecutor = uiBgExecutor;
@@ -182,12 +180,7 @@
         mActivityLaunchAnimator = activityLaunchAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
 
-        notifPipeline.addCollectionListener(new NotifCollectionListener() {
-            @Override
-            public void onEntryAdded(NotificationEntry entry) {
-                handleFullScreenIntent(entry);
-            }
-        });
+        launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
     }
 
     /**
@@ -549,38 +542,36 @@
     }
 
     @VisibleForTesting
-    void handleFullScreenIntent(NotificationEntry entry) {
-        if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
-            if (shouldSuppressFullScreenIntent(entry)) {
-                mLogger.logFullScreenIntentSuppressedByDnD(entry);
-            } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
-                mLogger.logFullScreenIntentNotImportantEnough(entry);
-            } else {
-                // Stop screensaver if the notification has a fullscreen intent.
-                // (like an incoming phone call)
-                mUiBgExecutor.execute(() -> {
-                    try {
-                        mDreamManager.awaken();
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                });
+    void launchFullScreenIntent(NotificationEntry entry) {
+        // Skip if device is in VR mode.
+        if (mPresenter.isDeviceInVrMode()) {
+            mLogger.logFullScreenIntentSuppressedByVR(entry);
+            return;
+        }
 
-                // not immersive & a fullscreen alert should be shown
-                final PendingIntent fullscreenIntent =
-                        entry.getSbn().getNotification().fullScreenIntent;
-                mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
-                try {
-                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
-                            entry.getKey());
-                    mCentralSurfaces.wakeUpForFullScreenIntent();
-                    fullscreenIntent.send();
-                    entry.notifyFullScreenIntentLaunched();
-                    mMetricsLogger.count("note_fullscreen", 1);
-                } catch (PendingIntent.CanceledException e) {
-                    // ignore
-                }
+        // Stop screensaver if the notification has a fullscreen intent.
+        // (like an incoming phone call)
+        mUiBgExecutor.execute(() -> {
+            try {
+                mDreamManager.awaken();
+            } catch (RemoteException e) {
+                e.printStackTrace();
             }
+        });
+
+        // not immersive & a fullscreen alert should be shown
+        final PendingIntent fullscreenIntent =
+                entry.getSbn().getNotification().fullScreenIntent;
+        mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
+        try {
+            EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+                    entry.getKey());
+            mCentralSurfaces.wakeUpForFullScreenIntent();
+            fullscreenIntent.send();
+            entry.notifyFullScreenIntentLaunched();
+            mMetricsLogger.count("note_fullscreen", 1);
+        } catch (PendingIntent.CanceledException e) {
+            // ignore
         }
     }
 
@@ -607,12 +598,4 @@
             mMainThreadHandler.post(mShadeController::collapsePanel);
         }
     }
-
-    private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
-        if (mPresenter.isDeviceInVrMode()) {
-            return true;
-        }
-
-        return entry.shouldSuppressFullScreenIntent();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 81edff4..1f0b96a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -96,19 +96,11 @@
         })
     }
 
-    fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) {
+    fun logFullScreenIntentSuppressedByVR(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
         }, {
-            "No Fullscreen intent: suppressed by DND: $str1"
-        })
-    }
-
-    fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) {
-        buffer.log(TAG, DEBUG, {
-            str1 = entry.logKey
-        }, {
-            "No Fullscreen intent: not important enough: $str1"
+            "No Fullscreen intent: suppressed by VR mode: $str1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 70af77e..8a49850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -133,7 +133,7 @@
         if (!row.isPinned()) {
             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
+        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
         mPendingRemoteInputView = clicked;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 492734e..de7bf3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -212,7 +212,7 @@
     private void updateWifiIconWithState(WifiIconState state) {
         if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
         if (state.visible && state.resId > 0) {
-            mIconController.setSignalIcon(mSlotWifi, state);
+            mIconController.setWifiIcon(mSlotWifi, state);
             mIconController.setIconVisibility(mSlotWifi, true);
         } else {
             mIconController.setIconVisibility(mSlotWifi, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index 6cd8c78..08599c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.view.InsetsVisibilities
+import android.view.WindowInsets.Type.InsetsType
 import android.view.WindowInsetsController.Appearance
 import android.view.WindowInsetsController.Behavior
 import com.android.internal.statusbar.LetterboxDetails
@@ -66,21 +66,21 @@
                 params.appearanceRegionsArray,
                 params.navbarColorManagedByIme,
                 params.behavior,
-                params.requestedVisibilities,
+                params.requestedVisibleTypes,
                 params.packageName,
                 params.letterboxesArray)
         }
     }
 
     fun onSystemBarAttributesChanged(
-        displayId: Int,
-        @Appearance originalAppearance: Int,
-        originalAppearanceRegions: Array<AppearanceRegion>,
-        navbarColorManagedByIme: Boolean,
-        @Behavior behavior: Int,
-        requestedVisibilities: InsetsVisibilities,
-        packageName: String,
-        letterboxDetails: Array<LetterboxDetails>
+            displayId: Int,
+            @Appearance originalAppearance: Int,
+            originalAppearanceRegions: Array<AppearanceRegion>,
+            navbarColorManagedByIme: Boolean,
+            @Behavior behavior: Int,
+            @InsetsType requestedVisibleTypes: Int,
+            packageName: String,
+            letterboxDetails: Array<LetterboxDetails>
     ) {
         lastSystemBarAttributesParams =
             SystemBarAttributesParams(
@@ -89,7 +89,7 @@
                 originalAppearanceRegions.toList(),
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails.toList())
 
@@ -104,7 +104,7 @@
 
         centralSurfaces.updateBubblesVisibility()
         statusBarStateController.setSystemBarAttributes(
-            appearance, behavior, requestedVisibilities, packageName)
+            appearance, behavior, requestedVisibleTypes, packageName)
     }
 
     private fun modifyAppearanceIfNeeded(
@@ -137,14 +137,14 @@
  * [SystemBarAttributesListener.onSystemBarAttributesChanged].
  */
 private data class SystemBarAttributesParams(
-    val displayId: Int,
-    @Appearance val appearance: Int,
-    val appearanceRegions: List<AppearanceRegion>,
-    val navbarColorManagedByIme: Boolean,
-    @Behavior val behavior: Int,
-    val requestedVisibilities: InsetsVisibilities,
-    val packageName: String,
-    val letterboxes: List<LetterboxDetails>,
+        val displayId: Int,
+        @Appearance val appearance: Int,
+        val appearanceRegions: List<AppearanceRegion>,
+        val navbarColorManagedByIme: Boolean,
+        @Behavior val behavior: Int,
+        @InsetsType val requestedVisibleTypes: Int,
+        val packageName: String,
+        val letterboxes: List<LetterboxDetails>,
 ) {
     val letterboxesArray = letterboxes.toTypedArray()
     val appearanceRegionsArray = appearanceRegions.toTypedArray()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
index e7d9221..678c2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
@@ -87,7 +87,7 @@
 
     private void updateDialogListeners() {
         if (shouldHideAffordance()) {
-            mKeyguardViewController.resetAlternateAuth(true);
+            mKeyguardViewController.hideAlternateBouncer(true);
         }
 
         for (Listener listener : mListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 06cd12d..946d7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -27,11 +27,26 @@
     /** True if we should display the mobile icons using the new status bar data pipeline. */
     fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
 
+    /**
+     * True if we should run the new mobile icons backend to get the logging.
+     *
+     * Does *not* affect whether we render the mobile icons using the new backend data. See
+     * [useNewMobileIcons] for that.
+     */
+    fun runNewMobileIconsBackend(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
+
     /** True if we should display the wifi icon using the new status bar data pipeline. */
     fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
 
-    // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
-    //   logging without getting the UI effects.
+    /**
+     * True if we should run the new wifi icon backend to get the logging.
+     *
+     * Does *not* affect whether we render the wifi icon using the new backend data. See
+     * [useNewWifiIcon] for that.
+     */
+    fun runNewWifiIconBackend(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
 
     /**
      * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 380017c..c7e0ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import javax.inject.Inject
@@ -50,6 +51,7 @@
     private val iconController: StatusBarIconController,
     private val iconsViewModelFactory: MobileIconsViewModel.Factory,
     @Application scope: CoroutineScope,
+    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) {
     private val mobileSubIds: Flow<List<Int>> =
         interactor.filteredSubscriptions.mapLatest { infos ->
@@ -66,8 +68,14 @@
     private val mobileSubIdsState: StateFlow<List<Int>> =
         mobileSubIds
             .onEach {
-                // Notify the icon controller here so that it knows to add icons
-                iconController.setNewMobileIconSubIds(it)
+                // Only notify the icon controller if we want to *render* the new icons.
+                // Note that this flow may still run if
+                // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+                // get the logging data without rendering.
+                if (statusBarPipelineFlags.useNewMobileIcons()) {
+                    // Notify the icon controller here so that it knows to add icons
+                    iconController.setNewMobileIconSubIds(it)
+                }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
new file mode 100644
index 0000000..b816364
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * This class serves as a bridge between the old UI classes and the new data pipeline.
+ *
+ * Once the new pipeline notifies [wifiViewModel] that the wifi icon should be visible, this class
+ * notifies [iconController] to inflate the wifi icon (if needed). After that, the [wifiViewModel]
+ * has sole responsibility for updating the wifi icon drawable, visibility, etc. and the
+ * [iconController] will not do any updates to the icon.
+ */
+@SysUISingleton
+class WifiUiAdapter
+@Inject
+constructor(
+    private val iconController: StatusBarIconController,
+    private val wifiViewModel: WifiViewModel,
+    private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) {
+    /**
+     * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
+     * view once we receive a valid icon from the data pipeline.
+     *
+     * NOTE: This should go away as we better integrate the data pipeline with the UI.
+     *
+     * @return the view model used for this particular group in the given [location].
+     */
+    fun bindGroup(
+        statusBarIconGroup: ViewGroup,
+        location: StatusBarLocation,
+    ): LocationBasedWifiViewModel {
+        val locationViewModel =
+            when (location) {
+                StatusBarLocation.HOME -> wifiViewModel.home
+                StatusBarLocation.KEYGUARD -> wifiViewModel.keyguard
+                StatusBarLocation.QS -> wifiViewModel.qs
+            }
+
+        statusBarIconGroup.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    locationViewModel.wifiIcon.collect { wifiIcon ->
+                        // Only notify the icon controller if we want to *render* the new icon.
+                        // Note that this flow may still run if
+                        // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
+                        // want to get the logging data without rendering.
+                        if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+                            iconController.setNewWifiIcon()
+                        }
+                    }
+                }
+            }
+        }
+
+        return locationViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 25537b9..345f8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,9 +30,7 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collect
@@ -62,26 +60,9 @@
         fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
     }
 
-    /**
-     * Binds the view to the appropriate view-model based on the given location. The view will
-     * continue to be updated following updates from the view-model.
-     */
-    @JvmStatic
-    fun bind(
-        view: ViewGroup,
-        wifiViewModel: WifiViewModel,
-        location: StatusBarLocation,
-    ): Binding {
-        return when (location) {
-            StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
-            StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
-            StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
-        }
-    }
-
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
     @JvmStatic
-    private fun bind(
+    fun bind(
         view: ViewGroup,
         viewModel: LocationBasedWifiViewModel,
     ): Binding {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 0cd9bd7..a45076b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -26,9 +26,8 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 
 /**
  * A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that
@@ -81,12 +80,11 @@
 
     private fun initView(
         slotName: String,
-        wifiViewModel: WifiViewModel,
-        location: StatusBarLocation,
+        wifiViewModel: LocationBasedWifiViewModel,
     ) {
         slot = slotName
         initDotView()
-        binding = WifiViewBinder.bind(this, wifiViewModel, location)
+        binding = WifiViewBinder.bind(this, wifiViewModel)
     }
 
     // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
@@ -116,14 +114,13 @@
         fun constructAndBind(
             context: Context,
             slot: String,
-            wifiViewModel: WifiViewModel,
-            location: StatusBarLocation,
+            wifiViewModel: LocationBasedWifiViewModel,
         ): ModernStatusBarWifiView {
             return (
                 LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
                     as ModernStatusBarWifiView
                 ).also {
-                    it.initView(slot, wifiViewModel, location)
+                    it.initView(slot, wifiViewModel)
                 }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 89b96b7..0782bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -145,7 +145,8 @@
                 else -> null
             }
         }
-        .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+                .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
+                .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
     private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 1d414745..7acdaff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -118,7 +118,10 @@
 
     private void updateDeviceState(int state) {
         Log.v(TAG, "updateDeviceState [state=" + state + "]");
-        Trace.beginSection("updateDeviceState [state=" + state + "]");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
+        }
         try {
             if (mDeviceState == state) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index bd2123a..69b55c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -33,6 +33,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -63,6 +64,7 @@
     private volatile int mNumConnectedDevices;
     // Assume tethering is available until told otherwise
     private volatile boolean mIsTetheringSupported = true;
+    private final boolean mIsTetheringSupportedConfig;
     private volatile boolean mHasTetherableWifiRegexs = true;
     private boolean mWaitingForTerminalState;
 
@@ -100,23 +102,29 @@
         mTetheringManager = context.getSystemService(TetheringManager.class);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mMainHandler = mainHandler;
-        mTetheringManager.registerTetheringEventCallback(
-                new HandlerExecutor(backgroundHandler), mTetheringCallback);
+        mIsTetheringSupportedConfig = context.getResources()
+                .getBoolean(R.bool.config_show_wifi_tethering);
+        if (mIsTetheringSupportedConfig) {
+            mTetheringManager.registerTetheringEventCallback(
+                    new HandlerExecutor(backgroundHandler), mTetheringCallback);
+        }
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
 
     /**
      * Whether hotspot is currently supported.
      *
-     * This will return {@code true} immediately on creation of the controller, but may be updated
-     * later. Callbacks from this controllers will notify if the state changes.
+     * This may return {@code true} immediately on creation of the controller, but may be updated
+     * later as capabilities are collected from System Server.
+     *
+     * Callbacks from this controllers will notify if the state changes.
      *
      * @return {@code true} if hotspot is supported (or we haven't been told it's not)
      * @see #addCallback
      */
     @Override
     public boolean isHotspotSupported() {
-        return mIsTetheringSupported && mHasTetherableWifiRegexs
+        return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
                 && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 637fac0..4cb41f3 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -22,7 +22,6 @@
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.os.PowerManager
-import android.os.SystemClock
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -35,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.wakelock.WakeLock
 
 /**
  * A generic controller that can temporarily display a new view in a new window.
@@ -54,6 +54,7 @@
     private val configurationController: ConfigurationController,
     private val powerManager: PowerManager,
     @LayoutRes private val viewLayoutRes: Int,
+    private val wakeLockBuilder: WakeLock.Builder,
 ) : CoreStartable {
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -64,7 +65,8 @@
         height = WindowManager.LayoutParams.WRAP_CONTENT
         type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
         flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
-            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
         format = PixelFormat.TRANSLUCENT
         setTrustedOverlay()
     }
@@ -84,6 +86,15 @@
     private var cancelViewTimeout: Runnable? = null
 
     /**
+     * A wakelock that is acquired when view is displayed and screen off,
+     * then released when view is removed.
+     */
+    private var wakeLock: WakeLock? = null
+
+    /** A string that keeps track of wakelock reason once it is acquired till it gets released */
+    private var wakeReasonAcquired: String? = null
+
+    /**
      * Displays the view with the provided [newInfo].
      *
      * This method handles inflating and attaching the view, then delegates to [updateView] to
@@ -113,11 +124,15 @@
             // the view to show over the dream state, so we should only wake up if the screen is
             // completely off.)
             if (!powerManager.isScreenOn) {
-                powerManager.wakeUp(
-                    SystemClock.uptimeMillis(),
-                    PowerManager.WAKE_REASON_APPLICATION,
-                    "com.android.systemui:${newInfo.wakeReason}",
-                )
+                wakeLock = wakeLockBuilder
+                    .setTag(newInfo.windowTitle)
+                    .setLevelsAndFlags(
+                        PowerManager.FULL_WAKE_LOCK or
+                        PowerManager.ACQUIRE_CAUSES_WAKEUP
+                    )
+                    .build()
+                wakeLock?.acquire(newInfo.wakeReason)
+                wakeReasonAcquired = newInfo.wakeReason
             }
             logger.logViewAddition(newInfo.windowTitle)
             inflateAndUpdateView(newInfo)
@@ -155,6 +170,7 @@
             it.copyFrom(windowLayoutParams)
             it.title = newInfo.windowTitle
         }
+        newView.keepScreenOn = true
         windowManager.addView(newView, paramsWithTitle)
         animateViewIn(newView)
     }
@@ -183,7 +199,10 @@
         val currentDisplayInfo = displayInfo ?: return
 
         val currentView = currentDisplayInfo.view
-        animateViewOut(currentView) { windowManager.removeView(currentView) }
+        animateViewOut(currentView) {
+            windowManager.removeView(currentView)
+            wakeLock?.release(wakeReasonAcquired)
+        }
 
         logger.logViewRemoval(removalReason)
         configurationController.removeCallback(displayScaleListener)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 87b6e8d..44e5ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
 import javax.inject.Inject
 
 /**
@@ -75,6 +76,7 @@
         private val falsingCollector: FalsingCollector,
         private val viewUtil: ViewUtil,
         private val vibratorHelper: VibratorHelper,
+        wakeLockBuilder: WakeLock.Builder,
 ) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
         context,
         logger,
@@ -84,6 +86,7 @@
         configurationController,
         powerManager,
         R.layout.chipbar,
+        wakeLockBuilder,
 ) {
 
     private lateinit var parent: ChipbarRootView
@@ -92,8 +95,6 @@
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
     }
 
-    override fun start() {}
-
     override fun updateView(
         newInfo: ChipbarInfo,
         currentView: ViewGroup
@@ -192,6 +193,8 @@
         )
     }
 
+    override fun start() {}
+
     override fun getTouchableRegion(view: View, outRect: Rect) {
         viewUtil.setRectToViewWindowLocation(view, outRect)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index f017126..b56c403 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -25,6 +25,8 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -59,6 +61,7 @@
     private final ActivityStarter mActivityStarter;
 
     private Dialog mSetupUserDialog;
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     @Inject
     public CreateUserActivity(UserCreator userCreator,
@@ -82,6 +85,10 @@
 
         mSetupUserDialog = createDialog();
         mSetupUserDialog.show();
+
+        getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                        OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                        mBackCallback);
     }
 
     @Override
@@ -125,10 +132,20 @@
 
     @Override
     public void onBackPressed() {
-        super.onBackPressed();
+        onBackInvoked();
+    }
+
+    private void onBackInvoked() {
         if (mSetupUserDialog != null) {
             mSetupUserDialog.dismiss();
         }
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
+        super.onDestroy();
     }
 
     private void addUserNow(String userName, Drawable userIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 6a23260..ffaf524 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -250,6 +250,10 @@
                         override fun onUserChanged(newUser: Int, userContext: Context) {
                             send()
                         }
+
+                        override fun onProfilesChanged(profiles: List<UserInfo>) {
+                            send()
+                        }
                     }
 
                 tracker.addCallback(callback, mainDispatcher.asExecutor())
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index f9d14cd..a374885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -28,6 +28,8 @@
 import android.view.WindowManagerGlobal
 import android.widget.Toast
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -58,6 +60,8 @@
     private val devicePolicyManager: DevicePolicyManager,
     private val refreshUsersScheduler: RefreshUsersScheduler,
     private val uiEventLogger: UiEventLogger,
+    resumeSessionReceiver: GuestResumeSessionReceiver,
+    resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver,
 ) {
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
@@ -65,6 +69,11 @@
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean = repository.isGuestUserResetting
 
+    init {
+        resumeSessionReceiver.register()
+        resetOrExitSessionReceiver.register()
+    }
+
     /** Notifies that the device has finished booting. */
     fun onDeviceBootCompleted() {
         applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt
new file mode 100644
index 0000000..12a0c03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/BrightnessProgressDrawable.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.util
+
+import android.content.pm.ActivityInfo
+import android.content.res.Resources
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.graphics.drawable.InsetDrawable
+
+/**
+ * [DrawableWrapper] to use in the progress of brightness slider.
+ *
+ * This drawable is used to change the bounds of the enclosed drawable depending on the level to
+ * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the
+ * edges is maintained.
+ *
+ * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
+ * is meant to be smaller than the rounded corner. The background should have rounded corners that
+ * are half of the height.
+ *
+ * This class also assumes that a "thumb" icon exists within the end's edge of the progress
+ * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb
+ * icon which puts the icon directly underneath the user's finger.
+ */
+class BrightnessProgressDrawable @JvmOverloads constructor(drawable: Drawable? = null) :
+    InsetDrawable(drawable, 0) {
+
+    companion object {
+        private const val MAX_LEVEL = 10000 // Taken from Drawable
+    }
+
+    override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+        onLevelChange(level)
+        return super.onLayoutDirectionChanged(layoutDirection)
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        super.onBoundsChange(bounds)
+        onLevelChange(level)
+    }
+
+    override fun onLevelChange(level: Int): Boolean {
+        val db = drawable?.bounds!!
+
+        // The thumb offset shifts the sun icon directly under the user's thumb
+        val thumbOffset = bounds.height() / 2
+        val width = bounds.width() * level / MAX_LEVEL + thumbOffset
+
+        // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
+        drawable?.setBounds(
+            bounds.left,
+            db.top,
+            width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()),
+            db.bottom
+        )
+        return super.onLevelChange(level)
+    }
+
+    override fun getConstantState(): ConstantState {
+        // This should not be null as it was created with a state in the constructor.
+        return RoundedCornerState(super.getConstantState()!!)
+    }
+
+    override fun getChangingConfigurations(): Int {
+        return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY
+    }
+
+    override fun canApplyTheme(): Boolean {
+        return (drawable?.canApplyTheme() ?: false) || super.canApplyTheme()
+    }
+
+    private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() {
+        override fun newDrawable(): Drawable {
+            return newDrawable(null, null)
+        }
+
+        override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable {
+            val wrapper = wrappedState.newDrawable(res, theme) as DrawableWrapper
+            return BrightnessProgressDrawable(wrapper.drawable)
+        }
+
+        override fun getChangingConfigurations(): Int {
+            return wrappedState.changingConfigurations
+        }
+
+        override fun canApplyTheme(): Boolean {
+            return true
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
index 99eb03b..1059d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -33,11 +33,6 @@
  * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
  * is meant to be smaller than the rounded corner. The background should have rounded corners that
  * are half of the height.
- *
- * This class also assumes that a "thumb" icon exists within the end's edge of the progress
- * drawable, and the slider's width, when interacted on, if offset by half the size of the thumb
- * icon which puts the icon directly underneath the user's finger.
- *
  */
 class RoundedCornerProgressDrawable @JvmOverloads constructor(
     drawable: Drawable? = null
@@ -59,16 +54,9 @@
 
     override fun onLevelChange(level: Int): Boolean {
         val db = drawable?.bounds!!
-
-        // The thumb offset shifts the sun icon directly under the user's thumb
-        val thumbOffset = bounds.height() / 2
-        val width = bounds.width() * level / MAX_LEVEL + thumbOffset
-
         // On 0, the width is bounds.height (a circle), and on MAX_LEVEL, the width is bounds.width
-        drawable?.setBounds(
-            bounds.left, db.top,
-            width.coerceAtMost(bounds.width()).coerceAtLeast(bounds.height()), db.bottom
-        )
+        val width = bounds.height() + (bounds.width() - bounds.height()) * level / MAX_LEVEL
+        drawable?.setBounds(bounds.left, db.top, bounds.left + width, db.bottom)
         return super.onLevelChange(level)
     }
 
@@ -103,4 +91,4 @@
             return true
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 8d77c4a..f320d07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -38,6 +38,11 @@
     long DEFAULT_MAX_TIMEOUT = 20000;
 
     /**
+     * Default wake-lock levels and flags.
+     */
+    int DEFAULT_LEVELS_AND_FLAGS = PowerManager.PARTIAL_WAKE_LOCK;
+
+    /**
      * @param why A tag that will be saved for sysui dumps.
      * @see android.os.PowerManager.WakeLock#acquire()
      **/
@@ -60,13 +65,21 @@
      * Creates a {@link WakeLock} that has a default release timeout.
      * @see android.os.PowerManager.WakeLock#acquire(long) */
     static WakeLock createPartial(Context context, String tag, long maxTimeout) {
-        return wrap(createPartialInner(context, tag), maxTimeout);
+        return wrap(createWakeLockInner(context, tag, DEFAULT_LEVELS_AND_FLAGS), maxTimeout);
+    }
+
+    /**
+     * Creates a {@link WakeLock} that has a default release timeout and flags.
+     */
+    static WakeLock createWakeLock(Context context, String tag, int flags, long maxTimeout) {
+        return wrap(createWakeLockInner(context, tag, flags), maxTimeout);
     }
 
     @VisibleForTesting
-    static PowerManager.WakeLock createPartialInner(Context context, String tag) {
+    static PowerManager.WakeLock createWakeLockInner(
+            Context context, String tag, int levelsAndFlags) {
         return context.getSystemService(PowerManager.class)
-                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
+                    .newWakeLock(levelsAndFlags, tag);
     }
 
     static Runnable wrapImpl(WakeLock w, Runnable r) {
@@ -131,6 +144,7 @@
     class Builder {
         private final Context mContext;
         private String mTag;
+        private int mLevelsAndFlags = DEFAULT_LEVELS_AND_FLAGS;
         private long mMaxTimeout = DEFAULT_MAX_TIMEOUT;
 
         @Inject
@@ -143,13 +157,18 @@
             return this;
         }
 
+        public Builder setLevelsAndFlags(int levelsAndFlags) {
+            this.mLevelsAndFlags = levelsAndFlags;
+            return this;
+        }
+
         public Builder setMaxTimeout(long maxTimeout) {
             this.mMaxTimeout = maxTimeout;
             return this;
         }
 
         public WakeLock build() {
-            return WakeLock.createPartial(mContext, mTag, mMaxTimeout);
+            return WakeLock.createWakeLock(mContext, mTag, mLevelsAndFlags, mMaxTimeout);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4e77514..a4384d5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,6 +25,7 @@
 import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
+import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -51,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -129,6 +131,7 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
+            FeatureFlags featureFlags,
             Executor sysuiMainExecutor) {
         if (bubblesOptional.isPresent()) {
             return new BubblesManager(context,
@@ -146,6 +149,7 @@
                     notifCollection,
                     notifPipeline,
                     sysUiState,
+                    featureFlags,
                     sysuiMainExecutor);
         } else {
             return null;
@@ -168,6 +172,7 @@
             CommonNotifCollection notifCollection,
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
+            FeatureFlags featureFlags,
             Executor sysuiMainExecutor) {
         mContext = context;
         mBubbles = bubbles;
@@ -352,6 +357,7 @@
                 });
             }
         };
+        mBubbles.setBubbleBarEnabled(featureFlags.isEnabled(WM_BUBBLE_BAR));
         mBubbles.setSysuiProxy(mSysuiProxy);
     }
 
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index c55ee61..e52225b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -106,6 +106,12 @@
                   android:finishOnCloseSystemDialogs="true"
                   android:excludeFromRecents="true" />
 
+        <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable"
+            android:exported="false"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true" />
+
         <provider
             android:name="androidx.startup.InitializationProvider"
             tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 52f8ef8..aa4469f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -54,7 +54,8 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
+import com.android.systemui.biometrics.SideFpsUiRequestSource;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
@@ -141,7 +142,7 @@
     @Mock
     private KeyguardViewController mKeyguardViewController;
     @Mock
-    private SidefpsController mSidefpsController;
+    private SideFpsController mSideFpsController;
     @Mock
     private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
     @Mock
@@ -189,11 +190,20 @@
                 mKeyguardStateController, mKeyguardSecurityViewFlipperController,
                 mConfigurationController, mFalsingCollector, mFalsingManager,
                 mUserSwitcherController, mFeatureFlags, mGlobalSettings,
-                mSessionTracker, Optional.of(mSidefpsController), mFalsingA11yDelegate).create(
+                mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate).create(
                 mSecurityCallback);
     }
 
     @Test
+    public void onInitConfiguresViewMode() {
+        mKeyguardSecurityContainerController.onInit();
+        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
+    }
+
+    @Test
     public void showSecurityScreen_canInflateAllModes() {
         SecurityMode[] modes = SecurityMode.values();
         for (SecurityMode mode : modes) {
@@ -336,48 +346,48 @@
     @Test
     public void onBouncerVisibilityChanged_allConditionsGood_sideFpsHintShown() {
         setupConditionsToEnableSideFpsHint();
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).show();
-        verify(mSidefpsController, never()).hide();
+        verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).hide(any());
     }
 
     @Test
     public void onBouncerVisibilityChanged_fpsSensorNotRunning_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
         setFingerprintDetectionRunning(false);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
     public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
         setSideFpsHintEnabledFromResources(false);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
     public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
         setupConditionsToEnableSideFpsHint();
         setNeedsStrongAuth(true);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -385,13 +395,13 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSidefpsController, atLeastOnce()).show();
-        reset(mSidefpsController);
+        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -399,13 +409,13 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSidefpsController, atLeastOnce()).show();
-        reset(mSidefpsController);
+        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onStartingToHide();
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -413,13 +423,13 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        verify(mSidefpsController, atLeastOnce()).show();
-        reset(mSidefpsController);
+        verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onPause();
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
@@ -427,12 +437,12 @@
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onResume(0);
 
-        verify(mSidefpsController).show();
-        verify(mSidefpsController, never()).hide();
+        verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).hide(any());
     }
 
     @Test
@@ -441,12 +451,12 @@
         setupConditionsToEnableSideFpsHint();
         setSideFpsHintEnabledFromResources(false);
         mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
-        reset(mSidefpsController);
+        reset(mSideFpsController);
 
         mKeyguardSecurityContainerController.onResume(0);
 
-        verify(mSidefpsController).hide();
-        verify(mSidefpsController, never()).show();
+        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+        verify(mSideFpsController, never()).show(any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5718835..6d043c5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -629,7 +629,7 @@
 
     @Test
     public void testNoStartAuthenticate_whenAboutToShowBouncer() {
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(
+        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(
                 /* bouncerIsOrWillBeShowing */ true, /* bouncerFullyShown */ false);
 
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -1620,7 +1620,7 @@
     }
 
     @Test
-    public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
+    public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
             throws RemoteException {
         // Preconditions for face auth to run
         keyguardNotGoingAway();
@@ -1637,7 +1637,9 @@
         faceAuthLockedOut();
         mTestableLooper.processAllMessages();
 
-        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+        // This is needed beccause we want to show face locked out error message whenever face auth
+        // is supposed to run.
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
     }
 
     @Test
@@ -1849,7 +1851,7 @@
     }
 
     private void setKeyguardBouncerVisibility(boolean isVisible) {
-        mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
+        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
         mTestableLooper.processAllMessages();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 4dd46ed..40b2cdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -152,7 +152,7 @@
     @Mock
     private UdfpsController mUdfpsController;
     @Mock
-    private SidefpsController mSidefpsController;
+    private SideFpsController mSideFpsController;
     @Mock
     private DisplayManager mDisplayManager;
     @Mock
@@ -255,7 +255,7 @@
 
         mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                 mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
-                () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController,
+                () -> mUdfpsController, () -> mSideFpsController, mStatusBarStateController,
                 mVibratorHelper);
 
         mAuthController.start();
@@ -288,7 +288,7 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
                 mStatusBarStateController, mVibratorHelper);
         authController.start();
 
@@ -318,7 +318,7 @@
         // This test requires an uninitialized AuthController.
         AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                 mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
-                mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+                mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
                 mStatusBarStateController, mVibratorHelper);
         authController.start();
 
@@ -1009,7 +1009,7 @@
                 FingerprintManager fingerprintManager,
                 FaceManager faceManager,
                 Provider<UdfpsController> udfpsControllerFactory,
-                Provider<SidefpsController> sidefpsControllerFactory,
+                Provider<SideFpsController> sidefpsControllerFactory,
                 StatusBarStateController statusBarStateController,
                 VibratorHelper vibratorHelper) {
             super(context, execution, commandQueue, activityTaskManager, windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
new file mode 100644
index 0000000..e7d5632
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -0,0 +1,472 @@
+/*
+ * 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.biometrics
+
+import android.animation.Animator
+import android.app.ActivityManager
+import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManagerGlobal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+import android.view.DisplayInfo
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.recents.OverviewProxyService
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+private const val SENSOR_ID = 1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SideFpsControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    @Mock lateinit var layoutInflater: LayoutInflater
+    @Mock lateinit var fingerprintManager: FingerprintManager
+    @Mock lateinit var windowManager: WindowManager
+    @Mock lateinit var activityTaskManager: ActivityTaskManager
+    @Mock lateinit var sideFpsView: View
+    @Mock lateinit var displayManager: DisplayManager
+    @Mock lateinit var overviewProxyService: OverviewProxyService
+    @Mock lateinit var handler: Handler
+    @Mock lateinit var dumpManager: DumpManager
+    @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
+    @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var overlayController: ISidefpsController
+    private lateinit var sideFpsController: SideFpsController
+
+    enum class DeviceConfig {
+        X_ALIGNED,
+        Y_ALIGNED_UNFOLDED,
+        Y_ALIGNED_FOLDED
+    }
+
+    private lateinit var deviceConfig: DeviceConfig
+    private lateinit var indicatorBounds: Rect
+    private lateinit var displayBounds: Rect
+    private lateinit var sensorLocation: SensorLocationInternal
+    private var displayWidth: Int = 0
+    private var displayHeight: Int = 0
+    private var boundsWidth: Int = 0
+    private var boundsHeight: Int = 0
+
+    @Before
+    fun setup() {
+        context.addMockSystemService(DisplayManager::class.java, displayManager)
+        context.addMockSystemService(WindowManager::class.java, windowManager)
+
+        whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
+        whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+            .thenReturn(mock(LottieAnimationView::class.java))
+        with(mock(ViewPropertyAnimator::class.java)) {
+            whenEver(sideFpsView.animate()).thenReturn(this)
+            whenEver(alpha(anyFloat())).thenReturn(this)
+            whenEver(setStartDelay(anyLong())).thenReturn(this)
+            whenEver(setDuration(anyLong())).thenReturn(this)
+            whenEver(setListener(any())).thenAnswer {
+                (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd(
+                    mock(Animator::class.java)
+                )
+                this
+            }
+        }
+    }
+
+    private fun testWithDisplay(
+        deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+        initInfo: DisplayInfo.() -> Unit = {},
+        windowInsets: WindowInsets = insetsForSmallNavbar(),
+        block: () -> Unit
+    ) {
+        this.deviceConfig = deviceConfig
+
+        when (deviceConfig) {
+            DeviceConfig.X_ALIGNED -> {
+                displayWidth = 2560
+                displayHeight = 1600
+                sensorLocation = SensorLocationInternal("", 2325, 0, 0)
+                boundsWidth = 160
+                boundsHeight = 84
+            }
+            DeviceConfig.Y_ALIGNED_UNFOLDED -> {
+                displayWidth = 2208
+                displayHeight = 1840
+                sensorLocation = SensorLocationInternal("", 0, 510, 0)
+                boundsWidth = 110
+                boundsHeight = 210
+            }
+            DeviceConfig.Y_ALIGNED_FOLDED -> {
+                displayWidth = 1080
+                displayHeight = 2100
+                sensorLocation = SensorLocationInternal("", 0, 590, 0)
+                boundsWidth = 110
+                boundsHeight = 210
+            }
+        }
+        indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+        displayBounds = Rect(0, 0, displayWidth, displayHeight)
+        var locations = listOf(sensorLocation)
+
+        whenEver(fingerprintManager.sensorPropertiesInternal)
+            .thenReturn(
+                listOf(
+                    FingerprintSensorPropertiesInternal(
+                        SENSOR_ID,
+                        SensorProperties.STRENGTH_STRONG,
+                        5 /* maxEnrollmentsPerUser */,
+                        listOf() /* componentInfo */,
+                        FingerprintSensorProperties.TYPE_POWER_BUTTON,
+                        true /* halControlsIllumination */,
+                        true /* resetLockoutRequiresHardwareAuthToken */,
+                        locations
+                    )
+                )
+            )
+
+        val displayInfo = DisplayInfo()
+        displayInfo.initInfo()
+        val dmGlobal = mock(DisplayManagerGlobal::class.java)
+        val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
+        whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
+        whenEver(windowManager.defaultDisplay).thenReturn(display)
+        whenEver(windowManager.maximumWindowMetrics)
+            .thenReturn(WindowMetrics(displayBounds, WindowInsets.CONSUMED))
+        whenEver(windowManager.currentWindowMetrics)
+            .thenReturn(WindowMetrics(displayBounds, windowInsets))
+
+        sideFpsController =
+            SideFpsController(
+                context.createDisplayContext(display),
+                layoutInflater,
+                fingerprintManager,
+                windowManager,
+                activityTaskManager,
+                overviewProxyService,
+                displayManager,
+                executor,
+                handler,
+                dumpManager
+            )
+
+        overlayController =
+            ArgumentCaptor.forClass(ISidefpsController::class.java)
+                .apply { verify(fingerprintManager).setSidefpsController(capture()) }
+                .value
+
+        block()
+    }
+
+    @Test
+    fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong())
+
+        overlayController.hide(SENSOR_ID)
+        executor.runAllReady()
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+
+    @Test
+    fun testShowsAndHides() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        verify(windowManager).addView(overlayCaptor.capture(), any())
+
+        reset(windowManager)
+        overlayController.hide(SENSOR_ID)
+        executor.runAllReady()
+
+        verify(windowManager, never()).addView(any(), any())
+        verify(windowManager).removeView(eq(overlayCaptor.value))
+    }
+
+    @Test
+    fun testShowsOnce() = testWithDisplay {
+        repeat(5) {
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+        }
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager, never()).removeView(any())
+    }
+
+    @Test
+    fun testHidesOnce() = testWithDisplay {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        repeat(5) {
+            overlayController.hide(SENSOR_ID)
+            executor.runAllReady()
+        }
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager).removeView(any())
+    }
+
+    @Test fun testIgnoredForKeyguard() = testWithDisplay { testIgnoredFor(REASON_AUTH_KEYGUARD) }
+
+    @Test
+    fun testShowsForMostSettings() = testWithDisplay {
+        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
+        testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
+    }
+
+    @Test
+    fun testIgnoredForVerySpecificSettings() = testWithDisplay {
+        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
+        testIgnoredFor(REASON_AUTH_SETTINGS)
+    }
+
+    private fun testIgnoredFor(reason: Int, ignored: Boolean = true) {
+        overlayController.show(SENSOR_ID, reason)
+        executor.runAllReady()
+
+        verify(windowManager, if (ignored) never() else times(1)).addView(any(), any())
+    }
+
+    @Test
+    fun showsWithTaskbar() =
+        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_0 }) {
+            hidesWithTaskbar(visible = true)
+        }
+
+    @Test
+    fun showsWithTaskbarOnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_0 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbar90() =
+        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_90 }) {
+            hidesWithTaskbar(visible = true)
+        }
+
+    @Test
+    fun showsWithTaskbar90OnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_90 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbar180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            { rotation = Surface.ROTATION_180 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbar270OnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_270 }
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbarCollapsedDown() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            { rotation = Surface.ROTATION_270 },
+            windowInsets = insetsForSmallNavbar()
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun showsWithTaskbarCollapsedDownOnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForSmallNavbar()
+        ) { hidesWithTaskbar(visible = true) }
+
+    @Test
+    fun hidesWithTaskbarDown() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForLargeNavbar()
+        ) { hidesWithTaskbar(visible = false) }
+
+    @Test
+    fun hidesWithTaskbarDownOnY() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            { rotation = Surface.ROTATION_270 },
+            windowInsets = insetsForLargeNavbar()
+        ) { hidesWithTaskbar(visible = true) }
+
+    private fun hidesWithTaskbar(visible: Boolean) {
+        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+        executor.runAllReady()
+
+        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
+        executor.runAllReady()
+
+        verify(windowManager).addView(any(), any())
+        verify(windowManager, never()).removeView(any())
+        verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
+    }
+
+    @Test
+    fun testIndicatorPlacementForXAlignedSensor() =
+        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED) {
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+        }
+
+    @Test
+    fun testIndicatorPlacementForYAlignedSensor() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+        }
+
+    @Test
+    fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
+        // By default all those tests assume the side fps sensor is available.
+
+        assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
+    }
+
+    @Test
+    fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
+        whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
+
+        assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
+    }
+
+    @Test
+    fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+            assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+        }
+
+    @Test
+    fun testLayoutParams_hasTrustedOverlayWindowFlag() =
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+            sideFpsController.overlayOffsets = sensorLocation
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+            val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+            assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+        }
+}
+
+private fun insetsForSmallNavbar() = insetsWithBottom(60)
+
+private fun insetsForLargeNavbar() = insetsWithBottom(100)
+
+private fun insetsWithBottom(bottom: Int) =
+    WindowInsets.Builder()
+        .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
+        .build()
+
+private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling")
+
+private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
+
+private fun settingsTask(cls: String) =
+    ActivityManager.RunningTaskInfo().apply {
+        topActivity = ComponentName.createRelative("com.android.settings", cls)
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
deleted file mode 100644
index 8d969d0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ /dev/null
@@ -1,493 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.animation.Animator
-import android.app.ActivityManager
-import android.app.ActivityTaskManager
-import android.content.ComponentName
-import android.graphics.Insets
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.biometrics.SensorProperties
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManagerGlobal
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.ISidefpsController
-import android.os.Handler
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.Display
-import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
-import android.view.DisplayInfo
-import android.view.LayoutInflater
-import android.view.Surface
-import android.view.View
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowMetrics
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.recents.OverviewProxyService
-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.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyLong
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val DISPLAY_ID = 2
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class SidefpsControllerTest : SysuiTestCase() {
-
-    @JvmField @Rule
-    var rule = MockitoJUnit.rule()
-
-    @Mock
-    lateinit var layoutInflater: LayoutInflater
-    @Mock
-    lateinit var fingerprintManager: FingerprintManager
-    @Mock
-    lateinit var windowManager: WindowManager
-    @Mock
-    lateinit var activityTaskManager: ActivityTaskManager
-    @Mock
-    lateinit var sidefpsView: View
-    @Mock
-    lateinit var displayManager: DisplayManager
-    @Mock
-    lateinit var overviewProxyService: OverviewProxyService
-    @Mock
-    lateinit var handler: Handler
-    @Captor
-    lateinit var overlayCaptor: ArgumentCaptor<View>
-    @Captor
-    lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
-
-    private val executor = FakeExecutor(FakeSystemClock())
-    private lateinit var overlayController: ISidefpsController
-    private lateinit var sideFpsController: SidefpsController
-
-    enum class DeviceConfig { X_ALIGNED, Y_ALIGNED_UNFOLDED, Y_ALIGNED_FOLDED }
-
-    private lateinit var deviceConfig: DeviceConfig
-    private lateinit var indicatorBounds: Rect
-    private lateinit var displayBounds: Rect
-    private lateinit var sensorLocation: SensorLocationInternal
-    private var displayWidth: Int = 0
-    private var displayHeight: Int = 0
-    private var boundsWidth: Int = 0
-    private var boundsHeight: Int = 0
-
-    @Before
-    fun setup() {
-        context.addMockSystemService(DisplayManager::class.java, displayManager)
-        context.addMockSystemService(WindowManager::class.java, windowManager)
-
-        whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
-        whenEver(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
-            .thenReturn(mock(LottieAnimationView::class.java))
-        with(mock(ViewPropertyAnimator::class.java)) {
-            whenEver(sidefpsView.animate()).thenReturn(this)
-            whenEver(alpha(anyFloat())).thenReturn(this)
-            whenEver(setStartDelay(anyLong())).thenReturn(this)
-            whenEver(setDuration(anyLong())).thenReturn(this)
-            whenEver(setListener(any())).thenAnswer {
-                (it.arguments[0] as Animator.AnimatorListener)
-                    .onAnimationEnd(mock(Animator::class.java))
-                this
-            }
-        }
-    }
-
-    private fun testWithDisplay(
-        deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
-        initInfo: DisplayInfo.() -> Unit = {},
-        windowInsets: WindowInsets = insetsForSmallNavbar(),
-        block: () -> Unit
-    ) {
-        this.deviceConfig = deviceConfig
-
-        when (deviceConfig) {
-            DeviceConfig.X_ALIGNED -> {
-                displayWidth = 2560
-                displayHeight = 1600
-                sensorLocation = SensorLocationInternal("", 2325, 0, 0)
-                boundsWidth = 160
-                boundsHeight = 84
-            }
-            DeviceConfig.Y_ALIGNED_UNFOLDED -> {
-                displayWidth = 2208
-                displayHeight = 1840
-                sensorLocation = SensorLocationInternal("", 0, 510, 0)
-                boundsWidth = 110
-                boundsHeight = 210
-            }
-            DeviceConfig.Y_ALIGNED_FOLDED -> {
-                displayWidth = 1080
-                displayHeight = 2100
-                sensorLocation = SensorLocationInternal("", 0, 590, 0)
-                boundsWidth = 110
-                boundsHeight = 210
-            }
-        }
-        indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
-        displayBounds = Rect(0, 0, displayWidth, displayHeight)
-        var locations = listOf(sensorLocation)
-
-        whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(
-            listOf(
-                FingerprintSensorPropertiesInternal(
-                    SENSOR_ID,
-                    SensorProperties.STRENGTH_STRONG,
-                    5 /* maxEnrollmentsPerUser */,
-                    listOf() /* componentInfo */,
-                    FingerprintSensorProperties.TYPE_POWER_BUTTON,
-                    true /* halControlsIllumination */,
-                    true /* resetLockoutRequiresHardwareAuthToken */,
-                    locations
-                )
-            )
-        )
-
-        val displayInfo = DisplayInfo()
-        displayInfo.initInfo()
-        val dmGlobal = mock(DisplayManagerGlobal::class.java)
-        val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
-        whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
-        whenEver(windowManager.defaultDisplay).thenReturn(display)
-        whenEver(windowManager.maximumWindowMetrics).thenReturn(
-                WindowMetrics(displayBounds, WindowInsets.CONSUMED)
-        )
-        whenEver(windowManager.currentWindowMetrics).thenReturn(
-            WindowMetrics(displayBounds, windowInsets)
-        )
-
-        sideFpsController = SidefpsController(
-            context.createDisplayContext(display), layoutInflater, fingerprintManager,
-            windowManager, activityTaskManager, overviewProxyService, displayManager, executor,
-            handler
-        )
-
-        overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply {
-            verify(fingerprintManager).setSidefpsController(capture())
-        }.value
-
-        block()
-    }
-
-    @Test
-    fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong())
-
-        overlayController.hide(SENSOR_ID)
-        executor.runAllReady()
-        verify(displayManager).unregisterDisplayListener(any())
-    }
-
-    @Test
-    fun testShowsAndHides() = testWithDisplay {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).addView(overlayCaptor.capture(), any())
-
-        reset(windowManager)
-        overlayController.hide(SENSOR_ID)
-        executor.runAllReady()
-
-        verify(windowManager, never()).addView(any(), any())
-        verify(windowManager).removeView(eq(overlayCaptor.value))
-    }
-
-    @Test
-    fun testShowsOnce() = testWithDisplay {
-        repeat(5) {
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-            executor.runAllReady()
-        }
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager, never()).removeView(any())
-    }
-
-    @Test
-    fun testHidesOnce() = testWithDisplay {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        repeat(5) {
-            overlayController.hide(SENSOR_ID)
-            executor.runAllReady()
-        }
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun testIgnoredForKeyguard() = testWithDisplay {
-        testIgnoredFor(REASON_AUTH_KEYGUARD)
-    }
-
-    @Test
-    fun testShowsForMostSettings() = testWithDisplay {
-        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
-        testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
-    }
-
-    @Test
-    fun testIgnoredForVerySpecificSettings() = testWithDisplay {
-        whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
-        testIgnoredFor(REASON_AUTH_SETTINGS)
-    }
-
-    private fun testIgnoredFor(reason: Int, ignored: Boolean = true) {
-        overlayController.show(SENSOR_ID, reason)
-        executor.runAllReady()
-
-        verify(windowManager, if (ignored) never() else times(1)).addView(any(), any())
-    }
-
-    @Test
-    fun showsWithTaskbar() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_0 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbarOnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_0 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar90() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_90 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar90OnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_90 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar180() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_180 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbar270OnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_270 }
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbarCollapsedDown() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_270 },
-        windowInsets = insetsForSmallNavbar()
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_180 },
-        windowInsets = insetsForSmallNavbar()
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    @Test
-    fun hidesWithTaskbarDown() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED,
-        { rotation = Surface.ROTATION_180 },
-        windowInsets = insetsForLargeNavbar()
-    ) {
-        hidesWithTaskbar(visible = false)
-    }
-
-    @Test
-    fun hidesWithTaskbarDownOnY() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-        { rotation = Surface.ROTATION_270 },
-        windowInsets = insetsForLargeNavbar()
-    ) {
-        hidesWithTaskbar(visible = true)
-    }
-
-    private fun hidesWithTaskbar(visible: Boolean) {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
-        executor.runAllReady()
-
-        verify(windowManager).addView(any(), any())
-        verify(windowManager, never()).removeView(any())
-        verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
-    }
-
-    @Test
-    fun testIndicatorPlacementForXAlignedSensor() = testWithDisplay(
-        deviceConfig = DeviceConfig.X_ALIGNED
-    ) {
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
-        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
-    }
-
-    @Test
-    fun testIndicatorPlacementForYAlignedSensor() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
-    ) {
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
-        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
-    }
-
-    @Test
-    fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
-        // By default all those tests assume the side fps sensor is available.
-
-        assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
-    }
-
-    @Test
-    fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
-        whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
-
-        assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
-    }
-
-    @Test
-    fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
-    ) {
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-        val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
-        assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
-    }
-
-    @Test
-    fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
-        deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
-    ) {
-        sideFpsController.overlayOffsets = sensorLocation
-        sideFpsController.updateOverlayParams(
-            windowManager.defaultDisplay,
-            indicatorBounds
-        )
-        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
-        executor.runAllReady()
-
-        verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
-        val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
-        assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
-    }
-}
-
-private fun insetsForSmallNavbar() = insetsWithBottom(60)
-private fun insetsForLargeNavbar() = insetsWithBottom(100)
-private fun insetsWithBottom(bottom: Int) = WindowInsets.Builder()
-    .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
-    .build()
-
-private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling")
-private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
-private fun settingsTask(cls: String) = ActivityManager.RunningTaskInfo().apply {
-    topActivity = ComponentName.createRelative("com.android.settings", cls)
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 6ec5a6c..4b459c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,7 +43,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -106,7 +106,7 @@
     @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
     @Mock private lateinit var featureFlags: FeatureFlags
-    @Mock private lateinit var bouncerInteractor: BouncerInteractor
+    @Mock private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -141,7 +141,7 @@
             configurationController, systemClock, keyguardStateController,
             unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
             controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
-            bouncerInteractor, isDebuggable
+            mPrimaryBouncerInteractor, isDebuggable
         )
         block()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 41c307a..1b5f9b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -20,6 +20,8 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -71,7 +73,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -108,9 +110,6 @@
 @RunWithLooper(setAsMainLooper = true)
 public class UdfpsControllerTest extends SysuiTestCase {
 
-    // Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things
-    // leaving SystemUI.
-    private static final int TEST_UDFPS_SENSOR_ID = 1;
     private static final long TEST_REQUEST_ID = 70;
 
     @Rule
@@ -121,7 +120,6 @@
 
     // Dependencies
     private FakeExecutor mBiometricsExecutor;
-    private Execution mExecution;
     @Mock
     private LayoutInflater mLayoutInflater;
     @Mock
@@ -194,20 +192,27 @@
     @Mock
     private AlternateUdfpsTouchProvider mAlternateTouchProvider;
     @Mock
-    private BouncerInteractor mBouncerInteractor;
+    private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
     // Capture listeners so that they can be used to send events
-    @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
+    @Captor
+    private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
     private IUdfpsOverlayController mOverlayController;
-    @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
-    @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
-    @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
-    @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
+    @Captor
+    private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+    @Captor
+    private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
+    @Captor
+    private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
+    @Captor
+    private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
     private ScreenLifecycle.Observer mScreenObserver;
+    private FingerprintSensorPropertiesInternal mOpticalProps;
+    private FingerprintSensorPropertiesInternal mUltrasonicProps;
 
     @Before
     public void setUp() {
-        mExecution = new FakeExecution();
+        Execution execution = new FakeExecution();
 
         when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
                 .thenReturn(mUdfpsView);
@@ -220,9 +225,7 @@
         when(mLayoutInflater.inflate(R.layout.udfps_fpm_other_view, null))
                 .thenReturn(mFpmOtherView);
         when(mEnrollView.getContext()).thenReturn(mContext);
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
-        final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
 
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
         componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
@@ -232,13 +235,25 @@
                 "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
                 "vendor/version/revision" /* softwareVersion */));
 
-        props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID,
+        mOpticalProps = new FingerprintSensorPropertiesInternal(1 /* sensorId */,
                 SensorProperties.STRENGTH_STRONG,
                 5 /* maxEnrollmentsPerUser */,
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequiresHardwareAuthToken */));
+                true /* resetLockoutRequiresHardwareAuthToken */);
+
+        mUltrasonicProps = new FingerprintSensorPropertiesInternal(2 /* sensorId */,
+                SensorProperties.STRENGTH_STRONG,
+                5 /* maxEnrollmentsPerUser */,
+                componentInfo,
+                FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+                true /* resetLockoutRequiresHardwareAuthToken */);
+
+        List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+        props.add(mOpticalProps);
+        props.add(mUltrasonicProps);
         when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+
         mFgExecutor = new FakeExecutor(new FakeSystemClock());
 
         // Create a fake background executor.
@@ -246,7 +261,7 @@
 
         mUdfpsController = new UdfpsController(
                 mContext,
-                mExecution,
+                execution,
                 mLayoutInflater,
                 mFingerprintManager,
                 mWindowManager,
@@ -276,18 +291,18 @@
                 mActivityLaunchAnimator,
                 Optional.of(mAlternateTouchProvider),
                 mBiometricsExecutor,
-                mBouncerInteractor);
+                mPrimaryBouncerInteractor);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
         mScreenObserver = mScreenObserverCaptor.getValue();
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams());
+        mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams());
         mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
     }
 
     @Test
     public void dozeTimeTick() throws RemoteException {
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         mUdfpsController.dozeTimeTick();
@@ -302,7 +317,7 @@
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -337,7 +352,7 @@
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -345,7 +360,7 @@
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
         if (stale) {
-            mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+            mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
             mFgExecutor.runAllReady();
         }
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
@@ -365,7 +380,7 @@
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -388,27 +403,27 @@
     @Test
     public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
         // GIVEN overlay was showing and the udfps bouncer is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
 
         // WHEN the overlay is hidden
-        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
         mFgExecutor.runAllReady();
 
         // THEN the udfps bouncer is reset
-        verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+        verify(mStatusBarKeyguardViewManager).hideAlternateBouncer(eq(true));
     }
 
     @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong());
 
-        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
         mFgExecutor.runAllReady();
 
         verify(mDisplayManager).unregisterDisplayListener(any());
@@ -438,12 +453,12 @@
                             }
 
                             // Initialize the overlay with old parameters.
-                            mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, oldParams);
+                            mUdfpsController.updateOverlayParams(mOpticalProps, oldParams);
 
                             // Show the overlay.
                             reset(mWindowManager);
                             mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID,
-                                    TEST_UDFPS_SENSOR_ID,
+                                    mOpticalProps.sensorId,
                                     BiometricOverlayConstants.REASON_ENROLL_ENROLLING,
                                     mUdfpsOverlayControllerCallback);
                             mFgExecutor.runAllReady();
@@ -451,7 +466,7 @@
 
                             // Update overlay parameters.
                             reset(mWindowManager);
-                            mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, newParams);
+                            mUdfpsController.updateOverlayParams(mOpticalProps, newParams);
                             mFgExecutor.runAllReady();
 
                             // Ensure the overlay was recreated.
@@ -473,18 +488,18 @@
         final int rotation = Surface.ROTATION_0;
 
         // Initialize the overlay.
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, rotation));
 
         // Show the overlay.
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mWindowManager).addView(any(), any());
 
         // Update overlay with the same parameters.
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, rotation));
         mFgExecutor.runAllReady();
@@ -526,13 +541,13 @@
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // Show the overlay.
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // Test ROTATION_0
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_0));
         MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -549,7 +564,7 @@
 
         // Test ROTATION_90
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_90));
         event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -565,7 +580,7 @@
 
         // Test ROTATION_270
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_270));
         event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -581,7 +596,7 @@
 
         // Test ROTATION_180
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_180));
         // ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -597,63 +612,108 @@
                 eq(expectedY), eq(expectedMinor), eq(expectedMajor));
     }
 
+    private void runForAllUdfpsTypes(
+            ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) {
+        for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+                mUltrasonicProps)) {
+            mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
+            sensorPropsConsumer.accept(sensorProps);
+        }
+    }
+
     @Test
-    public void fingerDown() throws RemoteException {
+    public void fingerDown() {
+        runForAllUdfpsTypes(this::fingerDownForSensor);
+    }
+
+    private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
+                mKeyguardUpdateMonitor);
+
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
-        // WHEN ACTION_DOWN is received
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricsExecutor.runAllReady();
         downEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
 
-        // FIX THIS TEST
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricsExecutor.runAllReady();
         moveEvent.recycle();
+
         mFgExecutor.runAllReady();
+
         // THEN FingerprintManager is notified about onPointerDown
         verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
                 eq(0f));
         verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
                 anyFloat(), anyFloat());
-        verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
         // AND display configuration begins
-        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+            verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        } else {
+            verify(mLatencyTracker, never()).onActionStart(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+            verify(mUdfpsView, never()).configureDisplay(any());
+        }
         verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        // AND onDisplayConfigured notifies FingerprintManager about onUiReady
-        mOnDisplayConfiguredCaptor.getValue().run();
-        mBiometricsExecutor.runAllReady();
-        InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
-        inOrder.verify(mAlternateTouchProvider).onUiReady();
-        inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+            mOnDisplayConfiguredCaptor.getValue().run();
+            mBiometricsExecutor.runAllReady();
+            InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+            inOrder.verify(mAlternateTouchProvider).onUiReady();
+            inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+        } else {
+            verify(mAlternateTouchProvider, never()).onUiReady();
+            verify(mLatencyTracker, never()).onActionEnd(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+        }
     }
 
     @Test
-    public void aodInterrupt() throws RemoteException {
+    public void aodInterrupt() {
+        runForAllUdfpsTypes(this::aodInterruptForSensor);
+    }
+
+    private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        mUdfpsController.cancelAodInterrupt();
+        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
         // GIVEN that the overlay is showing and screen is on and fp is running
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         // WHEN fingerprint is requested because of AOD interrupt
         mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
         mFgExecutor.runAllReady();
-        // THEN display configuration begins
-        // AND onDisplayConfigured notifies FingerprintManager about onUiReady
-        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
-        mOnDisplayConfiguredCaptor.getValue().run();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN display configuration begins
+            // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+            verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+            mOnDisplayConfiguredCaptor.getValue().run();
+        } else {
+            verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        }
         mBiometricsExecutor.runAllReady();
         verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
                 eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -663,54 +723,92 @@
     }
 
     @Test
-    public void cancelAodInterrupt() throws RemoteException {
+    public void cancelAodInterrupt() {
+        runForAllUdfpsTypes(this::cancelAodInterruptForSensor);
+    }
+
+    private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
-        // WHEN it is cancelled
-        mUdfpsController.cancelAodInterrupt();
-        // THEN the display is unconfigured
-        verify(mUdfpsView).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+            // WHEN it is cancelled
+            mUdfpsController.cancelAodInterrupt();
+            // THEN the display is unconfigured
+            verify(mUdfpsView).unconfigureDisplay();
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+            // WHEN it is cancelled
+            mUdfpsController.cancelAodInterrupt();
+            // THEN the display configuration is unchanged.
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptTimeout() throws RemoteException {
+    public void aodInterruptTimeout() {
+        runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor);
+    }
+
+    private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
-        // THEN the display is unconfigured
-        verify(mUdfpsView).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN the display is unconfigured.
+            verify(mUdfpsView).unconfigureDisplay();
+        } else {
+            // THEN the display configuration is unchanged.
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
+    public void aodInterruptCancelTimeoutActionOnFingerUp() {
+        runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor);
+    }
+
+    private void aodInterruptCancelTimeoutActionOnFingerUpForSensor(
+            FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+        reset(mUdfpsView);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the ACTION_UP event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the ACTION_UP event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN ACTION_UP is received
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -735,37 +833,54 @@
         moveEvent.recycle();
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the finger up event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the finger up event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
 
-        // THEN the display should be unconfigured once. If the timeout action is not
-        // cancelled, the display would be unconfigured twice which would cause two
-        // FP attempts.
-        verify(mUdfpsView, times(1)).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN the display should be unconfigured once. If the timeout action is not
+            // cancelled, the display would be unconfigured twice which would cause two
+            // FP attempts.
+            verify(mUdfpsView, times(1)).unconfigureDisplay();
+        } else {
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+    public void aodInterruptCancelTimeoutActionOnAcquired() {
+        runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor);
+    }
+
+    private void aodInterruptCancelTimeoutActionOnAcquiredForSensor(
+            FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+        reset(mUdfpsView);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the acquired event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the acquired event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN acquired is received
-        mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+        mOverlayController.onAcquired(sensorProps.sensorId,
                 BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
@@ -785,29 +900,43 @@
         moveEvent.recycle();
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the finger up event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the finger up event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
 
-        // THEN the display should be unconfigured once. If the timeout action is not
-        // cancelled, the display would be unconfigured twice which would cause two
-        // FP attempts.
-        verify(mUdfpsView, times(1)).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN the display should be unconfigured once. If the timeout action is not
+            // cancelled, the display would be unconfigured twice which would cause two
+            // FP attempts.
+            verify(mUdfpsView, times(1)).unconfigureDisplay();
+        } else {
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptScreenOff() throws RemoteException {
+    public void aodInterruptScreenOff() {
+        runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor);
+    }
+
+    private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN screen off
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOff();
         mFgExecutor.runAllReady();
 
         // WHEN aod interrupt is received
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
 
         // THEN display doesn't get configured because it's off
@@ -815,9 +944,16 @@
     }
 
     @Test
-    public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+    public void aodInterrupt_fingerprintNotRunning() {
+        runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor);
+    }
+
+    private void aodInterrupt_fingerprintNotRunningForSensor(
+            FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN showing overlay
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
                 mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
@@ -839,7 +975,7 @@
 
         // GIVEN that the overlay is showing and a11y touch exploration enabled
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -874,7 +1010,7 @@
 
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index e5c7a42..75629f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -30,7 +30,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
@@ -72,7 +72,7 @@
     protected @Mock UdfpsController mUdfpsController;
     protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
     protected @Mock KeyguardBouncer mBouncer;
-    protected @Mock BouncerInteractor mBouncerInteractor;
+    protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     protected FakeSystemClock mSystemClock = new FakeSystemClock();
@@ -86,9 +86,9 @@
     private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
     protected List<ShadeExpansionListener> mExpansionListeners;
 
-    private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
-            mAltAuthInterceptorCaptor;
-    protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+    private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateBouncer>
+            mAlternateBouncerCaptor;
+    protected StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
 
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallbackCaptor;
@@ -131,9 +131,9 @@
     }
 
     protected void captureAltAuthInterceptor() {
-        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
-                mAltAuthInterceptorCaptor.capture());
-        mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+        verify(mStatusBarKeyguardViewManager).setAlternateBouncer(
+                mAlternateBouncerCaptor.capture());
+        mAlternateBouncer = mAlternateBouncerCaptor.getValue();
     }
 
     protected void captureKeyguardStateControllerCallback() {
@@ -149,7 +149,7 @@
     protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
             boolean useModernBouncer) {
         mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
-        when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+        when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
                 useModernBouncer ? null : mBouncer);
         return new UdfpsKeyguardViewController(
                 mView,
@@ -167,6 +167,6 @@
                 mUdfpsController,
                 mActivityLaunchAnimator,
                 mFeatureFlags,
-                mBouncerInteractor);
+                mPrimaryBouncerInteractor);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 55b6194..16728b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -46,9 +46,9 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
-    private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+    private @Captor ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback>
             mBouncerExpansionCallbackCaptor;
-    private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
 
     @Override
     public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
@@ -63,7 +63,7 @@
 
         captureBouncerExpansionCallback();
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         mBouncerExpansionCallback.onVisibilityChanged(true);
 
         assertTrue(mController.shouldPauseAuth());
@@ -239,13 +239,13 @@
         sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
         assertTrue(mController.shouldPauseAuth());
 
-        mAltAuthInterceptor.showAlternateAuthBouncer(); // force show
+        mAlternateBouncer.showAlternateBouncer(); // force show
         assertFalse(mController.shouldPauseAuth());
-        assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+        assertTrue(mAlternateBouncer.isShowingAlternateBouncer());
 
-        mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show
+        mAlternateBouncer.hideAlternateBouncer(); // stop force show
         assertTrue(mController.shouldPauseAuth());
-        assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+        assertFalse(mAlternateBouncer.isShowingAlternateBouncer());
     }
 
     @Test
@@ -258,7 +258,7 @@
         mController.onViewDetached();
 
         // THEN remove alternate auth interceptor
-        verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
+        verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAlternateBouncer);
     }
 
     @Test
@@ -268,14 +268,15 @@
         captureAltAuthInterceptor();
 
         // GIVEN udfps bouncer isn't showing
-        mAltAuthInterceptor.hideAlternateAuthBouncer();
+        mAlternateBouncer.hideAlternateBouncer();
 
         // WHEN touch is observed outside the view
         mController.onTouchOutsideView();
 
         // THEN bouncer / alt auth methods are never called
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
-        verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
     }
 
     @Test
@@ -285,32 +286,33 @@
         captureAltAuthInterceptor();
 
         // GIVEN udfps bouncer is showing
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
 
         // WHEN touch is observed outside the view 200ms later (just within threshold)
         mSystemClock.advanceTime(200);
         mController.onTouchOutsideView();
 
         // THEN bouncer / alt auth methods are never called because not enough time has passed
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
-        verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean());
     }
 
     @Test
-    public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+    public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showPrimaryBouncer() {
         // GIVEN view is attached
         mController.onViewAttached();
         captureAltAuthInterceptor();
 
         // GIVEN udfps bouncer is showing
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
 
         // WHEN touch is observed outside the view 205ms later
         mSystemClock.advanceTime(205);
         mController.onTouchOutsideView();
 
         // THEN show the bouncer
-        verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(eq(true));
     }
 
     @Test
@@ -341,7 +343,7 @@
         when(mResourceContext.getString(anyInt())).thenReturn("test string");
 
         // WHEN status bar expansion is 0 but udfps bouncer is requested
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
 
         // THEN alpha is 255
         verify(mView).setUnpausedAlpha(255);
@@ -372,7 +374,7 @@
         captureKeyguardStateControllerCallback();
         captureAltAuthInterceptor();
         updateStatusBarExpansion(1f, true);
-        mAltAuthInterceptor.showAlternateAuthBouncer();
+        mAlternateBouncer.showAlternateBouncer();
         reset(mView);
 
         // WHEN we're transitioning to the full shade
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 7b19768..68e744e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.KeyguardBouncer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -59,14 +59,14 @@
     }
 
     override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
-        mBouncerInteractor =
-            BouncerInteractor(
+        mPrimaryBouncerInteractor =
+            PrimaryBouncerInteractor(
                 keyguardBouncerRepository,
                 mock(BouncerView::class.java),
                 mock(Handler::class.java),
                 mKeyguardStateController,
                 mock(KeyguardSecurityModel::class.java),
-                mock(BouncerCallbackInteractor::class.java),
+                mock(PrimaryBouncerCallbackInteractor::class.java),
                 mock(FalsingCollector::class.java),
                 mock(DismissCallbackRegistry::class.java),
                 mock(KeyguardBypassController::class.java),
@@ -86,7 +86,7 @@
 
             // WHEN the bouncer expansion is VISIBLE
             val job = mController.listenForBouncerExpansion(this)
-            keyguardBouncerRepository.setVisible(true)
+            keyguardBouncerRepository.setPrimaryVisible(true)
             keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
             yield()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 6bc7308..f8579ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -39,6 +39,8 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -66,6 +68,8 @@
     @Mock
     private SingleTapClassifier mSingleTapClassfier;
     @Mock
+    private LongTapClassifier mLongTapClassifier;
+    @Mock
     private DoubleTapClassifier mDoubleTapClassifier;
     @Mock
     private FalsingClassifier mClassifierA;
@@ -80,6 +84,7 @@
     private AccessibilityManager mAccessibilityManager;
 
     private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private final FalsingClassifier.Result mFalsedResult =
             FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
@@ -94,6 +99,7 @@
         when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mPassedResult);
         when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mPassedResult);
+        when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mPassedResult);
         mClassifiers.add(mClassifierA);
@@ -101,9 +107,9 @@
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
-                mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
-                mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
-                false);
+                mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
+                mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
+                mAccessibilityManager, false, mFakeFeatureFlags);
 
 
         ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
@@ -113,6 +119,7 @@
                 gestureCompleteListenerCaptor.capture());
 
         mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
+        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
     }
 
     @Test
@@ -212,7 +219,7 @@
     }
 
     @Test
-    public void testIsFalseTap_EmptyRecentEvents() {
+    public void testIsFalseSingleTap_EmptyRecentEvents() {
         // Ensure we look at prior events if recent events has already been emptied.
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
         when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
@@ -223,7 +230,7 @@
 
 
     @Test
-    public void testIsFalseTap_RobustCheck_NoFaceAuth() {
+    public void testIsFalseSingleTap_RobustCheck_NoFaceAuth() {
         when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
@@ -233,13 +240,50 @@
     }
 
     @Test
-    public void testIsFalseTap_RobustCheck_FaceAuth() {
+    public void testIsFalseSingleTap_RobustCheck_FaceAuth() {
         when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
         when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
         assertThat(mBrightLineFalsingManager.isFalseTap(NO_PENALTY)).isFalse();
     }
 
     @Test
+    public void testIsFalseLongTap_EmptyRecentEvents() {
+        // Ensure we look at prior events if recent events has already been emptied.
+        when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
+        when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
+
+        mBrightLineFalsingManager.isFalseLongTap(0);
+        verify(mLongTapClassifier).isTap(mMotionEventList, 0);
+    }
+
+    @Test
+    public void testIsFalseLongTap_FalseLongTap_NotFlagged() {
+        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false);
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+    }
+
+    @Test
+    public void testIsFalseLongTap_FalseLongTap() {
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue();
+    }
+
+    @Test
+    public void testIsFalseLongTap_RobustCheck_NoFaceAuth() {
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
+        when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(false);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+    }
+
+    @Test
+    public void testIsFalseLongTap_RobustCheck_FaceAuth() {
+        when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
+        when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+    }
+
+    @Test
     public void testIsFalseDoubleTap() {
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mPassedResult);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index b811aab..4281ee0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -32,6 +32,8 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -57,6 +59,8 @@
     @Mock
     private SingleTapClassifier mSingleTapClassifier;
     @Mock
+    private LongTapClassifier mLongTapClassifier;
+    @Mock
     private DoubleTapClassifier mDoubleTapClassifier;
     @Mock
     private FalsingClassifier mClassifierA;
@@ -71,6 +75,7 @@
     private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
     private final FalsingClassifier.Result mFalsedResult =
             FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
+    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     @Before
     public void setup() {
@@ -78,15 +83,17 @@
         when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
         when(mSingleTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+        when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
         when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
                 .thenReturn(mFalsedResult);
         mClassifiers.add(mClassifierA);
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
-                mMetricsLogger, mClassifiers, mSingleTapClassifier, mDoubleTapClassifier,
-                mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
-                false);
+                mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
+                mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
+                mAccessibilityManager, false, mFakeFeatureFlags);
+        mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
     }
 
     @Test
@@ -105,6 +112,13 @@
 
 
     @Test
+    public void testA11yDisablesLongTap() {
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isTrue();
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isFalse();
+    }
+
+    @Test
     public void testA11yDisablesDoubleTap() {
         assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue();
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c5a7de4..517804d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -36,10 +36,10 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 
 import org.junit.Before;
@@ -90,7 +90,13 @@
     ViewRootImpl mViewRoot;
 
     @Mock
-    BouncerCallbackInteractor mBouncerCallbackInteractor;
+    PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+
+    @Mock
+    DreamOverlayAnimationsController mAnimationsController;
+
+    @Mock
+    DreamOverlayStateController mStateController;
 
     DreamOverlayContainerViewController mController;
 
@@ -100,7 +106,7 @@
 
         when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
         when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-        when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(mBouncer);
+        when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(mBouncer);
         when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
 
         mController = new DreamOverlayContainerViewController(
@@ -115,7 +121,9 @@
                 MAX_BURN_IN_OFFSET,
                 BURN_IN_PROTECTION_UPDATE_INTERVAL,
                 MILLIS_UNTIL_FULL_JITTER,
-                mBouncerCallbackInteractor);
+                mPrimaryBouncerCallbackInteractor,
+                mAnimationsController,
+                mStateController);
     }
 
     @Test
@@ -159,8 +167,8 @@
 
     @Test
     public void testBouncerAnimation_doesNotApply() {
-        final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
-                ArgumentCaptor.forClass(BouncerExpansionCallback.class);
+        final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
+                ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         mController.onViewAttached();
         verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
 
@@ -170,8 +178,8 @@
 
     @Test
     public void testBouncerAnimation_updateBlur() {
-        final ArgumentCaptor<BouncerExpansionCallback> bouncerExpansionCaptor =
-                ArgumentCaptor.forClass(BouncerExpansionCallback.class);
+        final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
+                ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         mController.onViewAttached();
         verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
 
@@ -188,4 +196,31 @@
         verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
         verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
     }
+
+    @Test
+    public void testStartDreamEntryAnimationsOnAttachedNonLowLight() {
+        when(mStateController.isLowLightActive()).thenReturn(false);
+
+        mController.onViewAttached();
+
+        verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+        verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+    }
+
+    @Test
+    public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() {
+        when(mStateController.isLowLightActive()).thenReturn(true);
+
+        mController.onViewAttached();
+
+        verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+    }
+
+    @Test
+    public void testCancelDreamEntryAnimationsOnDetached() {
+        mController.onViewAttached();
+        mController.onViewDetached();
+
+        verify(mAnimationsController).cancelRunningEntryAnimations();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f370be1..f04a37f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -253,6 +253,7 @@
         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
         verify(mStateController).setOverlayActive(false);
         verify(mStateController).setLowLightActive(false);
+        verify(mStateController).setEntryAnimationsFinished(false);
     }
 
     @Test
@@ -273,27 +274,31 @@
 
     @Test
     public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
-        when(mDreamOverlayContainerView.getParent())
-                .thenReturn(mDreamOverlayContainerViewParent)
-                .thenReturn(null);
-
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
+        // Destroy the service.
+        mService.onDestroy();
+        mMainExecutor.runAllReady();
+
         // Inform the overlay service of dream starting.
         overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
-
-        // Destroy the service.
-        mService.onDestroy();
-
-        // Run executor tasks.
         mMainExecutor.runAllReady();
 
         verify(mWindowManager, never()).addView(any(), any());
     }
 
     @Test
+    public void testNeverRemoveDecorViewIfNotAdded() {
+        // Service destroyed before dream started.
+        mService.onDestroy();
+        mMainExecutor.runAllReady();
+
+        verify(mWindowManager, never()).removeView(any());
+    }
+
+    @Test
     public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index d1d32a1..c21c7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -234,4 +234,20 @@
         verify(mCallback, times(1)).onStateChanged();
         assertThat(stateController.isLowLightActive()).isTrue();
     }
+
+    @Test
+    public void testNotifyEntryAnimationsFinishedChanged() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor);
+
+        stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
+        assertThat(stateController.areEntryAnimationsFinished()).isFalse();
+
+        stateController.setEntryAnimationsFinished(true);
+        mExecutor.runAllReady();
+
+        verify(mCallback, times(1)).onStateChanged();
+        assertThat(stateController.areEntryAnimationsFinished()).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index aa02178..85c2819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -19,16 +19,20 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
+import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
 import android.net.ConnectivityManager;
@@ -55,6 +59,7 @@
 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;
 
@@ -69,7 +74,7 @@
             "{count, plural, =1 {# notification} other {# notifications}}";
 
     @Mock
-    DreamOverlayStatusBarView mView;
+    MockDreamOverlayStatusBarView mView;
     @Mock
     ConnectivityManager mConnectivityManager;
     @Mock
@@ -105,6 +110,9 @@
     @Mock
     DreamOverlayStateController mDreamOverlayStateController;
 
+    @Captor
+    private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
     private final Executor mMainExecutor = Runnable::run;
 
     DreamOverlayStatusBarViewController mController;
@@ -115,6 +123,8 @@
 
         when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator))
                 .thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
+        doCallRealMethod().when(mView).setVisibility(anyInt());
+        doCallRealMethod().when(mView).getVisibility();
 
         mController = new DreamOverlayStatusBarViewController(
                 mView,
@@ -454,12 +464,10 @@
     public void testStatusBarHiddenWhenSystemStatusBarShown() {
         mController.onViewAttached();
 
-        final ArgumentCaptor<StatusBarWindowStateListener>
-                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
-        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
-        callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+        updateEntryAnimationsFinished();
+        updateStatusBarWindowState(true);
 
-        verify(mView).setVisibility(View.INVISIBLE);
+        assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
     }
 
     @Test
@@ -467,29 +475,43 @@
         mController.onViewAttached();
         reset(mView);
 
-        final ArgumentCaptor<StatusBarWindowStateListener>
-                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
-        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
-        callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+        updateEntryAnimationsFinished();
+        updateStatusBarWindowState(false);
 
-        verify(mView).setVisibility(View.VISIBLE);
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
     public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
         mController.onViewAttached();
+        updateEntryAnimationsFinished();
         mController.onViewDetached();
         reset(mView);
 
-        final ArgumentCaptor<StatusBarWindowStateListener>
-                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
-        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
-        callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+        updateStatusBarWindowState(true);
 
         verify(mView, never()).setVisibility(anyInt());
     }
 
     @Test
+    public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() {
+        mController.onViewAttached();
+
+        // Trigger status bar window state change.
+        final StatusBarWindowStateListener listener = updateStatusBarWindowState(false);
+
+        // Verify no visibility change because dream not started.
+        verify(mView, never()).setVisibility(anyInt());
+
+        // Dream entry animations finished.
+        updateEntryAnimationsFinished();
+
+        // Trigger another status bar window state change, and verify visibility change.
+        listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
     public void testExtraStatusBarItemSetWhenItemsChange() {
         mController.onViewAttached();
         when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
@@ -507,16 +529,75 @@
     public void testLowLightHidesStatusBar() {
         when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
         mController.onViewAttached();
+        updateEntryAnimationsFinished();
 
-        verify(mView).setVisibility(View.INVISIBLE);
-        reset(mView);
-
-        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
         final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
                 ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
         verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
         callbackCapture.getValue().onStateChanged();
 
-        verify(mView).setVisibility(View.VISIBLE);
+        assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
+        reset(mView);
+
+        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+        callbackCapture.getValue().onStateChanged();
+
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() {
+        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+        mController.onViewAttached();
+
+        // No change to visibility because dream not fully started.
+        verify(mView, never()).setVisibility(anyInt());
+
+        // Dream entry animations finished.
+        updateEntryAnimationsFinished();
+
+        // Trigger state change and verify visibility changed.
+        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onStateChanged();
+
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
+        when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
+        final ArgumentCaptor<StatusBarWindowStateListener>
+                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
+        final StatusBarWindowStateListener listener = callbackCapture.getValue();
+        listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN);
+        return listener;
+    }
+
+    private void updateEntryAnimationsFinished() {
+        when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+
+        verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+        final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue();
+        callback.onStateChanged();
+    }
+
+    private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+        private int mVisibility = View.VISIBLE;
+
+        private MockDreamOverlayStatusBarView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setVisibility(int visibility) {
+            mVisibility = visibility;
+        }
+
+        @Override
+        public int getVisibility() {
+            return mVisibility;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index 3b9e398..b477592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -16,6 +16,7 @@
 package com.android.systemui.dreams.complication;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -29,16 +30,18 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
 
 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;
 
-import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 
 @SmallTest
@@ -77,9 +80,20 @@
     @Mock
     ComplicationLayoutParams mComplicationLayoutParams;
 
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
+
+    @Captor
+    private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
+
+    @Captor
+    private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
     @Complication.Category
     static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
 
+    private ComplicationHostViewController mController;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -91,6 +105,15 @@
         when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
         when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
         when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+
+        mController = new ComplicationHostViewController(
+                mComplicationHostView,
+                mLayoutEngine,
+                mDreamOverlayStateController,
+                mLifecycleOwner,
+                mViewModel);
+
+        mController.init();
     }
 
     /**
@@ -98,25 +121,12 @@
      */
     @Test
     public void testViewModelObservation() {
-        final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
-                ArgumentCaptor.forClass(Observer.class);
-        final ComplicationHostViewController controller = new ComplicationHostViewController(
-                mComplicationHostView,
-                mLayoutEngine,
-                mLifecycleOwner,
-                mViewModel);
-
-        controller.init();
-
-        verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
-                observerArgumentCaptor.capture());
-
         final Observer<Collection<ComplicationViewModel>> observer =
-                observerArgumentCaptor.getValue();
+                captureComplicationViewModelsObserver();
 
-        // Add complication and ensure it is added to the view.
+        // Add a complication and ensure it is added to the view.
         final HashSet<ComplicationViewModel> complications = new HashSet<>(
-                Arrays.asList(mComplicationViewModel));
+                Collections.singletonList(mComplicationViewModel));
         observer.onChanged(complications);
 
         verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
@@ -127,4 +137,48 @@
 
         verify(mLayoutEngine).removeComplication(eq(mComplicationId));
     }
+
+    @Test
+    public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
+        final Observer<Collection<ComplicationViewModel>> observer =
+                captureComplicationViewModelsObserver();
+
+        // Add a complication before entry animations are finished.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Collections.singletonList(mComplicationViewModel));
+        observer.onChanged(complications);
+
+        // The complication view should be set to invisible.
+        verify(mComplicationView).setVisibility(View.INVISIBLE);
+    }
+
+    @Test
+    public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
+        final Observer<Collection<ComplicationViewModel>> observer =
+                captureComplicationViewModelsObserver();
+
+        // Dream entry animations finished.
+        when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+        final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
+        stateCallback.onStateChanged();
+
+        // Add a complication after entry animations are finished.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Collections.singletonList(mComplicationViewModel));
+        observer.onChanged(complications);
+
+        // The complication view should not be set to invisible.
+        verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+    }
+
+    private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
+        verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+                mObserverCaptor.capture());
+        return mObserverCaptor.getValue();
+    }
+
+    private DreamOverlayStateController.Callback captureOverlayStateCallback() {
+        verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+        return mCallbackCaptor.getValue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 4b3b70e..9c22cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import android.content.pm.PackageManager.NameNotFoundException
 import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -246,6 +247,43 @@
     }
 
     @Test
+    fun readIntFlag() {
+        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
+        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, 12))).isEqualTo(12)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, 93))).isEqualTo(93)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, 8))).isEqualTo(22)
+        assertThat(mFeatureFlagsDebug.getInt(IntFlag(4, 234))).isEqualTo(48)
+    }
+
+    @Test
+    fun readResourceIntFlag() {
+        whenever(resources.getInteger(1001)).thenReturn(88)
+        whenever(resources.getInteger(1002)).thenReturn(61)
+        whenever(resources.getInteger(1003)).thenReturn(9342)
+        whenever(resources.getInteger(1004)).thenThrow(NotFoundException("unknown resource"))
+        whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource"))
+        whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource"))
+
+        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(20)
+        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
+        whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
+
+        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(1, 1001))).isEqualTo(88)
+        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(2, 1002))).isEqualTo(61)
+        assertThat(mFeatureFlagsDebug.getInt(ResourceIntFlag(3, 1003))).isEqualTo(20)
+
+        Assert.assertThrows(NotFoundException::class.java) {
+            mFeatureFlagsDebug.getInt(ResourceIntFlag(4, 1004))
+        }
+        // Test that resource is loaded (and validated) even when the setting is set.
+        //  This prevents developers from not noticing when they reference an invalid resource.
+        Assert.assertThrows(NotFoundException::class.java) {
+            mFeatureFlagsDebug.getInt(ResourceIntFlag(5, 1005))
+        }
+    }
+
+    @Test
     fun broadcastReceiver_IgnoresInvalidData() {
         addFlag(UnreleasedFlag(1))
         addFlag(ResourceBooleanFlag(2, 1002))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 9628ee9..7355319 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -35,6 +35,8 @@
     private val flagMap = mutableMapOf<Int, Flag<*>>()
     private val flagA = UnreleasedFlag(500)
     private val flagB = ReleasedFlag(501)
+    private val stringFlag = StringFlag(502, "abracadabra")
+    private val intFlag = IntFlag(503, 12)
 
     private lateinit var cmd: FlagCommand
 
@@ -44,26 +46,59 @@
 
         whenever(featureFlags.isEnabled(any(UnreleasedFlag::class.java))).thenReturn(false)
         whenever(featureFlags.isEnabled(any(ReleasedFlag::class.java))).thenReturn(true)
+        whenever(featureFlags.getString(any(StringFlag::class.java))).thenAnswer { invocation ->
+            (invocation.getArgument(0) as StringFlag).default
+        }
+        whenever(featureFlags.getInt(any(IntFlag::class.java))).thenAnswer { invocation ->
+            (invocation.getArgument(0) as IntFlag).default
+        }
+
         flagMap.put(flagA.id, flagA)
         flagMap.put(flagB.id, flagB)
+        flagMap.put(stringFlag.id, stringFlag)
+        flagMap.put(intFlag.id, intFlag)
 
         cmd = FlagCommand(featureFlags, flagMap)
     }
 
     @Test
-    fun readFlagCommand() {
+    fun readBooleanFlagCommand() {
         cmd.execute(pw, listOf(flagA.id.toString()))
         Mockito.verify(featureFlags).isEnabled(flagA)
     }
 
     @Test
-    fun setFlagCommand() {
+    fun readStringFlagCommand() {
+        cmd.execute(pw, listOf(stringFlag.id.toString()))
+        Mockito.verify(featureFlags).getString(stringFlag)
+    }
+
+    @Test
+    fun readIntFlag() {
+        cmd.execute(pw, listOf(intFlag.id.toString()))
+        Mockito.verify(featureFlags).getInt(intFlag)
+    }
+
+    @Test
+    fun setBooleanFlagCommand() {
         cmd.execute(pw, listOf(flagB.id.toString(), "on"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
     }
 
     @Test
-    fun toggleFlagCommand() {
+    fun setStringFlagCommand() {
+        cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar"))
+        Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar")
+    }
+
+    @Test
+    fun setIntFlag() {
+        cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123"))
+        Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123)
+    }
+
+    @Test
+    fun toggleBooleanFlagCommand() {
         cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
deleted file mode 100644
index 250cc48..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.flags;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.util.Pair;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Test;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-public class FlagsTest extends SysuiTestCase {
-
-    @Test
-    public void testDuplicateFlagIdCheckWorks() {
-        List<Pair<String, Flag<?>>> flags = collectFlags(DuplicateFlagContainer.class);
-        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
-
-        assertWithMessage(generateAssertionMessage(duplicates))
-                .that(duplicates.size()).isEqualTo(2);
-    }
-
-    @Test
-    public void testNoDuplicateFlagIds() {
-        List<Pair<String, Flag<?>>> flags = collectFlags(Flags.class);
-        Map<Integer, List<String>> duplicates = groupDuplicateFlags(flags);
-
-        assertWithMessage(generateAssertionMessage(duplicates))
-                .that(duplicates.size()).isEqualTo(0);
-    }
-
-    private String generateAssertionMessage(Map<Integer, List<String>> duplicates) {
-        StringBuilder stringBuilder = new StringBuilder();
-        stringBuilder.append("Duplicate flag keys found: {");
-        for (int id : duplicates.keySet()) {
-            stringBuilder
-                    .append(" ")
-                    .append(id)
-                    .append(": [")
-                    .append(String.join(", ", duplicates.get(id)))
-                    .append("]");
-        }
-        stringBuilder.append(" }");
-
-        return stringBuilder.toString();
-    }
-
-    private List<Pair<String, Flag<?>>> collectFlags(Class<?> clz) {
-        List<Pair<String, Flag<?>>> flags = new ArrayList<>();
-
-        Field[] fields = clz.getFields();
-
-        for (Field field : fields) {
-            Class<?> t = field.getType();
-            if (Flag.class.isAssignableFrom(t)) {
-                try {
-                    flags.add(Pair.create(field.getName(), (Flag<?>) field.get(null)));
-                } catch (IllegalAccessException e) {
-                    // no-op
-                }
-            }
-        }
-
-        return flags;
-    }
-
-    private Map<Integer, List<String>> groupDuplicateFlags(List<Pair<String, Flag<?>>> flags) {
-        Map<Integer, List<String>> grouping = new HashMap<>();
-
-        for (Pair<String, Flag<?>> flag : flags) {
-            grouping.putIfAbsent(flag.second.getId(), new ArrayList<>());
-            grouping.get(flag.second.getId()).add(flag.first);
-        }
-
-        Map<Integer, List<String>> result = new HashMap<>();
-        for (Integer id : grouping.keySet()) {
-            if (grouping.get(id).size() > 1) {
-                result.put(id, grouping.get(id));
-            }
-        }
-
-        return result;
-    }
-
-    private static class DuplicateFlagContainer {
-        public static final BooleanFlag A_FLAG = new UnreleasedFlag(0);
-        public static final BooleanFlag B_FLAG = new UnreleasedFlag(0);
-        public static final StringFlag C_FLAG = new StringFlag(0);
-
-        public static final BooleanFlag D_FLAG = new UnreleasedFlag(1);
-
-        public static final DoubleFlag E_FLAG = new DoubleFlag(3);
-        public static final DoubleFlag F_FLAG = new DoubleFlag(3);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt
new file mode 100644
index 0000000..2b556f1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagsTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth
+import java.lang.StringBuilder
+import java.util.ArrayList
+import java.util.HashMap
+import org.junit.Test
+
+@SmallTest
+class FlagsTest : SysuiTestCase() {
+    @Test
+    fun testDuplicateFlagIdCheckWorks() {
+        val flags = Flags.collectFlagsInClass(DuplicateFlagContainer)
+        val duplicates = groupDuplicateFlags(flags)
+        Truth.assertWithMessage(generateAssertionMessage(duplicates))
+            .that(duplicates.size)
+            .isEqualTo(2)
+    }
+
+    @Test
+    fun testNoDuplicateFlagIds() {
+        val flags = Flags.collectFlagsInClass(Flags)
+        val duplicates = groupDuplicateFlags(flags)
+        Truth.assertWithMessage(generateAssertionMessage(duplicates))
+            .that(duplicates.size)
+            .isEqualTo(0)
+    }
+
+    private fun generateAssertionMessage(duplicates: Map<Int, List<String>>): String {
+        val stringBuilder = StringBuilder()
+        stringBuilder.append("Duplicate flag keys found: {")
+        for (id in duplicates.keys) {
+            stringBuilder
+                .append(" ")
+                .append(id)
+                .append(": [")
+                .append(java.lang.String.join(", ", duplicates[id]))
+                .append("]")
+        }
+        stringBuilder.append(" }")
+        return stringBuilder.toString()
+    }
+
+    private fun groupDuplicateFlags(flags: Map<String, Flag<*>>): Map<Int, List<String>> {
+        val grouping: MutableMap<Int, MutableList<String>> = HashMap()
+        for (flag in flags) {
+            grouping.putIfAbsent(flag.value.id, ArrayList())
+            grouping[flag.value.id]!!.add(flag.key)
+        }
+        val result: MutableMap<Int, List<String>> = HashMap()
+        for (id in grouping.keys) {
+            if (grouping[id]!!.size > 1) {
+                result[id] = grouping[id]!!
+            }
+        }
+        return result
+    }
+
+    private object DuplicateFlagContainer {
+        val A_FLAG: BooleanFlag = UnreleasedFlag(0)
+        val B_FLAG: BooleanFlag = UnreleasedFlag(0)
+        val C_FLAG = StringFlag(0)
+        val D_FLAG: BooleanFlag = UnreleasedFlag(1)
+        val E_FLAG = DoubleFlag(3)
+        val F_FLAG = DoubleFlag(3)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 2c3ddd5..b6780a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -57,6 +57,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -105,6 +106,7 @@
     private @Mock ScreenOffAnimationController mScreenOffAnimationController;
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private @Mock ScreenOnCoordinator mScreenOnCoordinator;
+    private @Mock ShadeController mShadeController;
     private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
     private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -307,6 +309,7 @@
                 mScreenOnCoordinator,
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
+                () -> mShadeController,
                 mNotificationShadeWindowControllerLazy,
                 () -> mActivityLaunchAnimator);
         mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
deleted file mode 100644
index 640e6dc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WorkLockActivityTest extends SysuiTestCase {
-    private static final @UserIdInt int USER_ID = 270;
-    private static final String CALLING_PACKAGE_NAME = "com.android.test";
-
-    private @Mock UserManager mUserManager;
-    private @Mock PackageManager mPackageManager;
-    private @Mock Context mContext;
-    private @Mock BroadcastDispatcher mBroadcastDispatcher;
-    private @Mock Drawable mDrawable;
-    private @Mock Drawable mBadgedDrawable;
-
-    private WorkLockActivity mActivity;
-
-    private static class WorkLockActivityTestable extends WorkLockActivity {
-        WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher,
-                UserManager userManager, PackageManager packageManager) {
-            super(broadcastDispatcher, userManager, packageManager);
-            attachBaseContext(baseContext);
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-        mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager,
-                mPackageManager);
-    }
-
-    @Test
-    public void testGetBadgedIcon() throws Exception {
-        ApplicationInfo info = new ApplicationInfo();
-        when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(),
-                eq(USER_ID))).thenReturn(info);
-        when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable);
-        when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn(
-                mBadgedDrawable);
-        mActivity.setIntent(new Intent()
-                .putExtra(Intent.EXTRA_USER_ID, USER_ID)
-                .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME));
-
-        assertEquals(mBadgedDrawable, mActivity.getBadgedIcon());
-    }
-
-    @Test
-    public void testUnregisteredFromDispatcher() {
-        mActivity.unregisterBroadcastReceiver();
-        verify(mBroadcastDispatcher).unregisterReceiver(any());
-        verify(mContext, never()).unregisterReceiver(any());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
new file mode 100644
index 0000000..c7f1dec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.graphics.drawable.Drawable
+import android.os.Looper
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkLockActivityTest : SysuiTestCase() {
+    private val context: Context = mock()
+    private val userManager: UserManager = mock()
+    private val packageManager: PackageManager = mock()
+    private val broadcastDispatcher: BroadcastDispatcher = mock()
+    private val drawable: Drawable = mock()
+    private val badgedDrawable: Drawable = mock()
+    private lateinit var activity: WorkLockActivity
+
+    private class WorkLockActivityTestable
+    constructor(
+        baseContext: Context,
+        broadcastDispatcher: BroadcastDispatcher,
+        userManager: UserManager,
+        packageManager: PackageManager,
+    ) : WorkLockActivity(broadcastDispatcher, userManager, packageManager) {
+        init {
+            attachBaseContext(baseContext)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare()
+        }
+        activity =
+            WorkLockActivityTestable(
+                baseContext = context,
+                broadcastDispatcher = broadcastDispatcher,
+                userManager = userManager,
+                packageManager = packageManager
+            )
+    }
+
+    @Test
+    fun testGetBadgedIcon() {
+        val info = ApplicationInfo()
+        whenever(
+                packageManager.getApplicationInfoAsUser(
+                    eq(CALLING_PACKAGE_NAME),
+                    any<ApplicationInfoFlags>(),
+                    eq(USER_ID)
+                )
+            )
+            .thenReturn(info)
+        whenever(packageManager.getApplicationIcon(ArgumentMatchers.eq(info))).thenReturn(drawable)
+        whenever(userManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID))))
+            .thenReturn(badgedDrawable)
+        activity.intent =
+            Intent()
+                .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+                .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME)
+        assertEquals(badgedDrawable, activity.badgedIcon)
+    }
+
+    @Test
+    fun testUnregisteredFromDispatcher() {
+        activity.unregisterBroadcastReceiver()
+        verify(broadcastDispatcher).unregisterReceiver(any())
+        verify(context, never()).unregisterReceiver(nullable())
+    }
+
+    @Test
+    fun onBackPressed_finishActivity() {
+        assertFalse(activity.isFinishing)
+
+        activity.onBackPressed()
+
+        assertFalse(activity.isFinishing)
+    }
+
+    companion object {
+        @UserIdInt private val USER_ID = 270
+        private const val CALLING_PACKAGE_NAME = "com.android.test"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index f18acba..0fb181d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -23,14 +23,11 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.yield
 
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig(
+/** Fake implementation of a quick affordance data source. */
+class FakeKeyguardQuickAffordanceConfig(
     override val key: String,
+    override val pickerName: String = key,
+    override val pickerIconResourceId: Int = 0,
 ) : KeyguardQuickAffordanceConfig {
 
     var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
new file mode 100644
index 0000000..d2422ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardQuickAffordanceSelectionManager()
+    }
+
+    @Test
+    fun setSelections() =
+        runBlocking(IMMEDIATE) {
+            var affordanceIdsBySlotId: Map<String, List<String>>? = null
+            val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
+            val slotId1 = "slot1"
+            val slotId2 = "slot2"
+            val affordanceId1 = "affordance1"
+            val affordanceId2 = "affordance2"
+            val affordanceId3 = "affordance3"
+
+            underTest.setSelections(
+                slotId = slotId1,
+                affordanceIds = listOf(affordanceId1),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId1),
+                ),
+            )
+
+            underTest.setSelections(
+                slotId = slotId2,
+                affordanceIds = listOf(affordanceId2),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId1),
+                    slotId2 to listOf(affordanceId2),
+                )
+            )
+
+            underTest.setSelections(
+                slotId = slotId1,
+                affordanceIds = listOf(affordanceId1, affordanceId3),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId1, affordanceId3),
+                    slotId2 to listOf(affordanceId2),
+                )
+            )
+
+            underTest.setSelections(
+                slotId = slotId1,
+                affordanceIds = listOf(affordanceId3),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId3),
+                    slotId2 to listOf(affordanceId2),
+                )
+            )
+
+            underTest.setSelections(
+                slotId = slotId2,
+                affordanceIds = listOf(),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId3),
+                    slotId2 to listOf(),
+                )
+            )
+
+            job.cancel()
+        }
+
+    private suspend fun assertSelections(
+        observed: Map<String, List<String>>?,
+        expected: Map<String, List<String>>,
+    ) {
+        assertThat(underTest.getSelections()).isEqualTo(expected)
+        assertThat(observed).isEqualTo(expected)
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 61a3f9f..2bd8e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -50,7 +50,7 @@
         MockitoAnnotations.initMocks(this)
         whenever(controller.intent).thenReturn(INTENT_1)
 
-        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index c05beef..5178154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -59,6 +59,7 @@
 
         underTest =
             QuickAccessWalletKeyguardQuickAffordanceConfig(
+                mock(),
                 walletController,
                 activityStarter,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
new file mode 100644
index 0000000..5a7f2bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+    private lateinit var config1: FakeKeyguardQuickAffordanceConfig
+    private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
+        config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+        underTest =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs = setOf(config1, config2),
+            )
+    }
+
+    @Test
+    fun setSelections() =
+        runBlocking(IMMEDIATE) {
+            var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
+            val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+            val slotId1 = "slot1"
+            val slotId2 = "slot2"
+
+            underTest.setSelections(slotId1, listOf(config1.key))
+            assertSelections(
+                configsBySlotId,
+                mapOf(
+                    slotId1 to listOf(config1),
+                ),
+            )
+
+            underTest.setSelections(slotId2, listOf(config2.key))
+            assertSelections(
+                configsBySlotId,
+                mapOf(
+                    slotId1 to listOf(config1),
+                    slotId2 to listOf(config2),
+                ),
+            )
+
+            underTest.setSelections(slotId1, emptyList())
+            underTest.setSelections(slotId2, listOf(config1.key))
+            assertSelections(
+                configsBySlotId,
+                mapOf(
+                    slotId1 to emptyList(),
+                    slotId2 to listOf(config1),
+                ),
+            )
+
+            job.cancel()
+        }
+
+    @Test
+    fun getAffordancePickerRepresentations() {
+        assertThat(underTest.getAffordancePickerRepresentations())
+            .isEqualTo(
+                listOf(
+                    KeyguardQuickAffordancePickerRepresentation(
+                        id = config1.key,
+                        name = config1.pickerName,
+                        iconResourceId = config1.pickerIconResourceId,
+                    ),
+                    KeyguardQuickAffordancePickerRepresentation(
+                        id = config2.key,
+                        name = config2.pickerName,
+                        iconResourceId = config2.pickerIconResourceId,
+                    ),
+                )
+            )
+    }
+
+    @Test
+    fun getSlotPickerRepresentations() {
+        assertThat(underTest.getSlotPickerRepresentations())
+            .isEqualTo(
+                listOf(
+                    KeyguardSlotPickerRepresentation(
+                        id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                        maxSelectedAffordances = 1,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                        maxSelectedAffordances = 1,
+                    ),
+                )
+            )
+    }
+
+    private suspend fun assertSelections(
+        observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
+        expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
+    ) {
+        assertThat(observed).isEqualTo(expected)
+        assertThat(underTest.getSelections())
+            .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
+        expected.forEach { (slotId, configs) ->
+            assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+        }
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9ab9d3..ade83cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.keyguard.data.repository
 
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.doze.DozeHost
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -46,6 +49,8 @@
     @Mock private lateinit var dozeHost: DozeHost
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -55,10 +60,12 @@
 
         underTest =
             KeyguardRepositoryImpl(
-                statusBarStateController,
-                keyguardStateController,
-                dozeHost,
-                wakefulnessLifecycle,
+                    statusBarStateController,
+                    dozeHost,
+                    wakefulnessLifecycle,
+                    biometricUnlockController,
+                    keyguardStateController,
+                    keyguardUpdateMonitor,
             )
     }
 
@@ -190,7 +197,7 @@
     }
 
     @Test
-    fun wakefullness() = runBlockingTest {
+    fun wakefulness() = runBlockingTest {
         val values = mutableListOf<WakefulnessModel>()
         val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
 
@@ -217,4 +224,72 @@
         job.cancel()
         verify(wakefulnessLifecycle).removeObserver(captor.value)
     }
+
+    @Test
+    fun isUdfpsSupported() = runBlockingTest {
+        whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
+        assertThat(underTest.isUdfpsSupported()).isTrue()
+
+        whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
+        assertThat(underTest.isUdfpsSupported()).isFalse()
+    }
+
+    @Test
+    fun isBouncerShowing() = runBlockingTest {
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+        var latest: Boolean? = null
+        val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        val captor = argumentCaptor<KeyguardStateController.Callback>()
+        verify(keyguardStateController).addCallback(captor.capture())
+
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+        captor.value.onBouncerShowingChanged()
+        assertThat(latest).isTrue()
+
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+        captor.value.onBouncerShowingChanged()
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun biometricUnlockState() = runBlockingTest {
+        val values = mutableListOf<BiometricUnlockModel>()
+        val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+        val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+        verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+        captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+        assertThat(values)
+            .isEqualTo(
+                listOf(
+                    // Initial value will be NONE, followed by onModeChanged() call
+                    BiometricUnlockModel.NONE,
+                    BiometricUnlockModel.NONE,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+                    BiometricUnlockModel.SHOW_BOUNCER,
+                    BiometricUnlockModel.ONLY_WAKE,
+                    BiometricUnlockModel.UNLOCK_COLLAPSING,
+                    BiometricUnlockModel.DISMISS_BOUNCER,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+                )
+            )
+
+        job.cancel()
+        verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 64913c7c..27d5d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -128,11 +128,15 @@
 
             assertThat(steps.size).isEqualTo(3)
             assertThat(steps[0])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
             assertThat(steps[1])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+                )
             assertThat(steps[2])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+                )
             job.cancel()
         }
 
@@ -174,15 +178,22 @@
     }
 
     private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
-        assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+        assertThat(steps[0])
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
         fractions.forEachIndexed { index, fraction ->
             assertThat(steps[index + 1])
                 .isEqualTo(
-                    TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+                    TransitionStep(
+                        AOD,
+                        LOCKSCREEN,
+                        fraction.toFloat(),
+                        TransitionState.RUNNING,
+                        OWNER_NAME
+                    )
                 )
         }
         assertThat(steps[steps.size - 1])
-            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
 
         assertThat(wtfHandler.failed).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
deleted file mode 100644
index 3a61c57..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerCallbackInteractorTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.KeyguardBouncer
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class BouncerCallbackInteractorTest : SysuiTestCase() {
-    private val bouncerCallbackInteractor = BouncerCallbackInteractor()
-    @Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback
-    @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback)
-        bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
-    }
-
-    @Test
-    fun testOnFullyShown() {
-        bouncerCallbackInteractor.dispatchFullyShown()
-        verify(bouncerExpansionCallback).onFullyShown()
-    }
-
-    @Test
-    fun testOnFullyHidden() {
-        bouncerCallbackInteractor.dispatchFullyHidden()
-        verify(bouncerExpansionCallback).onFullyHidden()
-    }
-
-    @Test
-    fun testOnExpansionChanged() {
-        bouncerCallbackInteractor.dispatchExpansionChanged(5f)
-        verify(bouncerExpansionCallback).onExpansionChanged(5f)
-    }
-
-    @Test
-    fun testOnVisibilityChanged() {
-        bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
-        verify(bouncerExpansionCallback).onVisibilityChanged(false)
-    }
-
-    @Test
-    fun testOnStartingToHide() {
-        bouncerCallbackInteractor.dispatchStartingToHide()
-        verify(bouncerExpansionCallback).onStartingToHide()
-    }
-
-    @Test
-    fun testOnStartingToShow() {
-        bouncerCallbackInteractor.dispatchStartingToShow()
-        verify(bouncerExpansionCallback).onStartingToShow()
-    }
-
-    @Test
-    fun testOnKeyguardReset() {
-        bouncerCallbackInteractor.dispatchReset()
-        verify(keyguardResetCallback).onKeyguardReset()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 7116cc1..8b6603d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,10 +25,14 @@
 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.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
@@ -37,6 +41,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Before
 import org.junit.Test
@@ -189,6 +195,8 @@
                     /* startActivity= */ true,
                 ),
             )
+
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -213,10 +221,20 @@
         whenever(expandable.activityLaunchController()).thenReturn(animationController)
 
         homeControls =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+        val quickAccessWallet =
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        val qrCodeScanner =
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+            )
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +247,8 @@
                                 ),
                             KeyguardQuickAffordancePosition.BOTTOM_END to
                                 listOf(
-                                    object :
-                                        FakeKeyguardQuickAffordanceConfig(
-                                            BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                                        ) {},
-                                    object :
-                                        FakeKeyguardQuickAffordanceConfig(
-                                            BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                                        ) {},
+                                    quickAccessWallet,
+                                    qrCodeScanner,
                                 ),
                         ),
                     ),
@@ -244,6 +256,11 @@
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
                 activityStarter = activityStarter,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+                    },
+                repository = { quickAffordanceRepository },
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index ae32ba6..3364535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,22 +22,31 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.yield
 import org.junit.Before
@@ -47,6 +56,7 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +72,7 @@
     private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
@@ -71,20 +82,25 @@
         repository.setKeyguardShowing(true)
 
         homeControls =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
         quickAccessWallet =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
         qrCodeScanner =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+            )
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+            }
 
         underTest =
             KeyguardQuickAffordanceInteractor(
@@ -107,6 +123,8 @@
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
                 activityStarter = activityStarter,
+                featureFlags = featureFlags,
+                repository = { quickAffordanceRepository },
             )
     }
 
@@ -210,6 +228,270 @@
             job.cancel()
         }
 
+    @Test
+    fun select() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            qrCodeScanner.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf<String, List<String>>(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            var startConfig: KeyguardQuickAffordanceModel? = null
+            val job1 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                    .onEach { startConfig = it }
+                    .launchIn(this)
+            var endConfig: KeyguardQuickAffordanceModel? = null
+            val job2 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                    .onEach { endConfig = it }
+                    .launchIn(this)
+
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                                "::${homeControls.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(homeControls.key),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            underTest.select(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                quickAccessWallet.key
+            )
+            yield()
+            yield()
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                                "::${quickAccessWallet.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(quickAccessWallet.key),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
+            yield()
+            yield()
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                                "::${quickAccessWallet.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+                                "::${qrCodeScanner.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(quickAccessWallet.key),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(qrCodeScanner.key),
+                    )
+                )
+
+            job1.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun `unselect - one`() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            qrCodeScanner.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+
+            var startConfig: KeyguardQuickAffordanceModel? = null
+            val job1 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                    .onEach { startConfig = it }
+                    .launchIn(this)
+            var endConfig: KeyguardQuickAffordanceModel? = null
+            val job2 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                    .onEach { endConfig = it }
+                    .launchIn(this)
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+            yield()
+            yield()
+
+            underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+                                "::${quickAccessWallet.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(quickAccessWallet.key),
+                    )
+                )
+
+            underTest.unselect(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                quickAccessWallet.key
+            )
+            yield()
+            yield()
+
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf<String, List<String>>(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            job1.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun `unselect - all`() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            qrCodeScanner.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+            yield()
+            yield()
+
+            underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
+            yield()
+            yield()
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(quickAccessWallet.key),
+                    )
+                )
+
+            underTest.unselect(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                null,
+            )
+            yield()
+            yield()
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf<String, List<String>>(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+        }
+
     companion object {
         private val ICON: Icon = mock {
             whenever(this.contentDescription)
@@ -220,5 +502,6 @@
                 )
         }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0424c28..6333b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -54,9 +54,11 @@
     @Test
     fun `transition collectors receives only appropriate events`() =
         runBlocking(IMMEDIATE) {
-            var goneToAodSteps = mutableListOf<TransitionStep>()
+            var lockscreenToAodSteps = mutableListOf<TransitionStep>()
             val job1 =
-                underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this)
+                underTest.lockscreenToAodTransition
+                    .onEach { lockscreenToAodSteps.add(it) }
+                    .launchIn(this)
 
             var aodToLockscreenSteps = mutableListOf<TransitionStep>()
             val job2 =
@@ -70,14 +72,14 @@
             steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
             steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
             steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
-            steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
-            steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
-            steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
 
             steps.forEach { repository.sendTransitionStep(it) }
 
             assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
-            assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8))
+            assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
 
             job1.cancel()
             job2.cancel()
@@ -119,10 +121,8 @@
     fun keyguardStateTests() =
         runBlocking(IMMEDIATE) {
             var finishedSteps = mutableListOf<KeyguardState>()
-            val job1 =
+            val job =
                 underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
-            var startedSteps = mutableListOf<KeyguardState>()
-            val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this)
 
             val steps = mutableListOf<TransitionStep>()
 
@@ -137,10 +137,60 @@
             steps.forEach { repository.sendTransitionStep(it) }
 
             assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
-            assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE))
 
-            job1.cancel()
-            job2.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun finishedKeyguardTransitionStepTests() =
+        runBlocking(IMMEDIATE) {
+            var finishedSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.finishedKeyguardTransitionStep
+                    .onEach { finishedSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+            job.cancel()
+        }
+
+    @Test
+    fun startedKeyguardTransitionStepTests() =
+        runBlocking(IMMEDIATE) {
+            var startedSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.startedKeyguardTransitionStep
+                    .onEach { startedSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+            job.cancel()
         }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..db9c4e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
+    private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
+    @Mock
+    private lateinit var mPrimaryBouncerExpansionCallback:
+        KeyguardBouncer.PrimaryBouncerExpansionCallback
+    @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
+            mPrimaryBouncerExpansionCallback
+        )
+        mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+    }
+
+    @Test
+    fun testOnFullyShown() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
+        verify(mPrimaryBouncerExpansionCallback).onFullyShown()
+    }
+
+    @Test
+    fun testOnFullyHidden() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
+        verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
+    }
+
+    @Test
+    fun testOnExpansionChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
+        verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
+    }
+
+    @Test
+    fun testOnVisibilityChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
+    }
+
+    @Test
+    fun testOnStartingToHide() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
+    }
+
+    @Test
+    fun testOnStartingToShow() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
+    }
+
+    @Test
+    fun testOnKeyguardReset() {
+        mPrimaryBouncerCallbackInteractor.dispatchReset()
+        verify(keyguardResetCallback).onKeyguardReset()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
similarity index 60%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 5743b2f..c85f7b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -53,59 +53,59 @@
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
-class BouncerInteractorTest : SysuiTestCase() {
+class PrimaryBouncerInteractorTest : SysuiTestCase() {
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var repository: KeyguardBouncerRepository
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
     @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
+    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
-    private lateinit var bouncerInteractor: BouncerInteractor
+    private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         DejankUtils.setImmediate(true)
-        bouncerInteractor =
-            BouncerInteractor(
+        mPrimaryBouncerInteractor =
+            PrimaryBouncerInteractor(
                 repository,
                 bouncerView,
                 mainHandler,
                 keyguardStateController,
                 keyguardSecurityModel,
-                bouncerCallbackInteractor,
+                mPrimaryBouncerCallbackInteractor,
                 falsingCollector,
                 dismissCallbackRegistry,
                 keyguardBypassController,
                 keyguardUpdateMonitor,
             )
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        `when`(repository.show.value).thenReturn(null)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        `when`(repository.primaryBouncerShow.value).thenReturn(null)
         `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
     }
 
     @Test
     fun testShow_isScrimmed() {
-        bouncerInteractor.show(true)
+        mPrimaryBouncerInteractor.show(true)
         verify(repository).setShowMessage(null)
         verify(repository).setOnScreenTurnedOff(false)
         verify(repository).setKeyguardAuthenticated(null)
-        verify(repository).setHide(false)
-        verify(repository).setStartingToHide(false)
-        verify(repository).setScrimmed(true)
+        verify(repository).setPrimaryHide(false)
+        verify(repository).setPrimaryStartingToHide(false)
+        verify(repository).setPrimaryScrimmed(true)
         verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
-        verify(repository).setShowingSoon(true)
+        verify(repository).setPrimaryShowingSoon(true)
         verify(keyguardStateController).notifyBouncerShowing(true)
-        verify(bouncerCallbackInteractor).dispatchStartingToShow()
-        verify(repository).setVisible(true)
-        verify(repository).setShow(any(KeyguardBouncerModel::class.java))
-        verify(repository).setShowingSoon(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
+        verify(repository).setPrimaryVisible(true)
+        verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
+        verify(repository).setPrimaryShowingSoon(false)
     }
 
     @Test
@@ -117,60 +117,60 @@
     fun testShow_keyguardIsDone() {
         `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
         verify(keyguardStateController, never()).notifyBouncerShowing(true)
-        verify(bouncerCallbackInteractor, never()).dispatchStartingToShow()
+        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
     }
 
     @Test
     fun testHide() {
-        bouncerInteractor.hide()
+        mPrimaryBouncerInteractor.hide()
         verify(falsingCollector).onBouncerHidden()
         verify(keyguardStateController).notifyBouncerShowing(false)
-        verify(repository).setShowingSoon(false)
-        verify(repository).setVisible(false)
-        verify(repository).setHide(true)
-        verify(repository).setShow(null)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(repository).setPrimaryVisible(false)
+        verify(repository).setPrimaryHide(true)
+        verify(repository).setPrimaryShow(null)
     }
 
     @Test
     fun testExpansion() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        bouncerInteractor.setPanelExpansion(0.6f)
+        mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
         verify(repository).setPanelExpansion(0.6f)
-        verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
     }
 
     @Test
     fun testExpansion_fullyShown() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
-        verify(bouncerCallbackInteractor).dispatchFullyShown()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
     }
 
     @Test
     fun testExpansion_fullyHidden() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
-        verify(repository).setVisible(false)
-        verify(repository).setShow(null)
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+        verify(repository).setPrimaryVisible(false)
+        verify(repository).setPrimaryShow(null)
         verify(falsingCollector).onBouncerHidden()
-        verify(bouncerCallbackInteractor).dispatchReset()
-        verify(bouncerCallbackInteractor).dispatchFullyHidden()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
     }
 
     @Test
     fun testExpansion_startingToHide() {
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        bouncerInteractor.setPanelExpansion(0.1f)
-        verify(repository).setStartingToHide(true)
-        verify(bouncerCallbackInteractor).dispatchStartingToHide()
+        mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+        verify(repository).setPrimaryStartingToHide(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
     }
 
     @Test
     fun testShowMessage() {
-        bouncerInteractor.showMessage("abc", null)
+        mPrimaryBouncerInteractor.showMessage("abc", null)
         verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
     }
 
@@ -178,100 +178,100 @@
     fun testDismissAction() {
         val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
         val cancelAction = mock(Runnable::class.java)
-        bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+        mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
         verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
     }
 
     @Test
     fun testUpdateResources() {
-        bouncerInteractor.updateResources()
+        mPrimaryBouncerInteractor.updateResources()
         verify(repository).setResourceUpdateRequests(true)
     }
 
     @Test
     fun testNotifyKeyguardAuthenticated() {
-        bouncerInteractor.notifyKeyguardAuthenticated(true)
+        mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
         verify(repository).setKeyguardAuthenticated(true)
     }
 
     @Test
     fun testOnScreenTurnedOff() {
-        bouncerInteractor.onScreenTurnedOff()
+        mPrimaryBouncerInteractor.onScreenTurnedOff()
         verify(repository).setOnScreenTurnedOff(true)
     }
 
     @Test
     fun testSetKeyguardPosition() {
-        bouncerInteractor.setKeyguardPosition(0f)
+        mPrimaryBouncerInteractor.setKeyguardPosition(0f)
         verify(repository).setKeyguardPosition(0f)
     }
 
     @Test
     fun testNotifyKeyguardAuthenticatedHandled() {
-        bouncerInteractor.notifyKeyguardAuthenticatedHandled()
+        mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
         verify(repository).setKeyguardAuthenticated(null)
     }
 
     @Test
     fun testNotifyUpdatedResources() {
-        bouncerInteractor.notifyUpdatedResources()
+        mPrimaryBouncerInteractor.notifyUpdatedResources()
         verify(repository).setResourceUpdateRequests(false)
     }
 
     @Test
     fun testSetBackButtonEnabled() {
-        bouncerInteractor.setBackButtonEnabled(true)
+        mPrimaryBouncerInteractor.setBackButtonEnabled(true)
         verify(repository).setIsBackButtonEnabled(true)
     }
 
     @Test
     fun testStartDisappearAnimation() {
         val runnable = mock(Runnable::class.java)
-        bouncerInteractor.startDisappearAnimation(runnable)
-        verify(repository).setStartDisappearAnimation(any(Runnable::class.java))
+        mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
     }
 
     @Test
     fun testIsFullShowing() {
-        `when`(repository.isVisible.value).thenReturn(true)
+        `when`(repository.primaryBouncerVisible.value).thenReturn(true)
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        assertThat(bouncerInteractor.isFullyShowing()).isTrue()
-        `when`(repository.isVisible.value).thenReturn(false)
-        assertThat(bouncerInteractor.isFullyShowing()).isFalse()
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+        `when`(repository.primaryBouncerVisible.value).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
     }
 
     @Test
     fun testIsScrimmed() {
-        `when`(repository.isScrimmed.value).thenReturn(true)
-        assertThat(bouncerInteractor.isScrimmed()).isTrue()
-        `when`(repository.isScrimmed.value).thenReturn(false)
-        assertThat(bouncerInteractor.isScrimmed()).isFalse()
+        `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
+        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+        `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
     }
 
     @Test
     fun testIsInTransit() {
-        `when`(repository.showingSoon.value).thenReturn(true)
-        assertThat(bouncerInteractor.isInTransit()).isTrue()
-        `when`(repository.showingSoon.value).thenReturn(false)
-        assertThat(bouncerInteractor.isInTransit()).isFalse()
+        `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+        `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+        assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        assertThat(bouncerInteractor.isInTransit()).isTrue()
+        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
     }
 
     @Test
     fun testIsAnimatingAway() {
-        `when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(bouncerInteractor.isAnimatingAway()).isTrue()
-        `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        assertThat(bouncerInteractor.isAnimatingAway()).isFalse()
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+        `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
     }
 
     @Test
     fun testWillDismissWithAction() {
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
-        assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
+        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
-        assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
+        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
     }
 }
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 f73d1ec..78148c4 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
@@ -23,10 +23,14 @@
 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.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -41,6 +45,8 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.max
 import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runBlockingTest
@@ -82,20 +88,13 @@
             .thenReturn(RETURNED_BURN_IN_OFFSET)
 
         homeControlsQuickAffordanceConfig =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
         quickAccessWalletAffordanceConfig =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
         qrCodeScannerAffordanceConfig =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
         registry =
             FakeKeyguardQuickAffordanceRegistry(
                 mapOf(
@@ -116,6 +115,18 @@
         whenever(userTracker.userHandle).thenReturn(mock())
         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs =
+                    setOf(
+                        homeControlsQuickAffordanceConfig,
+                        quickAccessWalletAffordanceConfig,
+                        qrCodeScannerAffordanceConfig,
+                    ),
+            )
         underTest =
             KeyguardBottomAreaViewModel(
                 keyguardInteractor = keyguardInteractor,
@@ -127,6 +138,11 @@
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
                         activityStarter = activityStarter,
+                        featureFlags =
+                            FakeFeatureFlags().apply {
+                                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+                            },
+                        repository = { quickAffordanceRepository },
                     ),
                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 burnInHelperWrapper = burnInHelperWrapper,
@@ -576,5 +592,6 @@
     companion object {
         private const val DEFAULT_BURN_IN_OFFSET = 5
         private const val RETURNED_BURN_IN_OFFSET = 3
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index c8e8943..6ca34e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -49,6 +49,7 @@
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -119,6 +120,7 @@
         MediaPlayerData.clear()
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testPlayerOrdering() {
         // Test values: key, data, last active time
@@ -295,6 +297,7 @@
         }
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testOrderWithSmartspace_prioritized() {
         testPlayerOrdering()
@@ -312,6 +315,7 @@
         assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
         testPlayerOrdering()
@@ -328,6 +332,7 @@
         assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testOrderWithSmartspace_notPrioritized() {
         testPlayerOrdering()
@@ -346,6 +351,7 @@
         assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
         testPlayerOrdering()
@@ -382,6 +388,8 @@
             MediaPlayerData.playerKeys().elementAt(0)
         )
     }
+
+    @Ignore("b/253229241")
     @Test
     fun testSwipeDismiss_logged() {
         mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -389,6 +397,7 @@
         verify(logger).logSwipeDismiss()
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testSettingsButton_logged() {
         mediaCarouselController.settingsButton.callOnClick()
@@ -396,6 +405,7 @@
         verify(logger).logCarouselSettings()
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeQs_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -406,6 +416,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeQqs_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -416,6 +427,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeLockscreen_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -426,6 +438,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testLocationChangeDream_logged() {
         mediaCarouselController.onDesiredLocationChanged(
@@ -436,6 +449,7 @@
         verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testRecommendationRemoved_logged() {
         val packageName = "smartspace package"
@@ -449,6 +463,7 @@
         verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testMediaLoaded_ScrollToActivePlayer() {
         listener.value.onMediaDataLoaded(
@@ -506,6 +521,7 @@
         )
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
         listener.value.onSmartspaceMediaDataLoaded(
@@ -549,6 +565,7 @@
         assertEquals(playerIndex, 0)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
         var result = false
@@ -560,6 +577,7 @@
         assertEquals(true, result)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
         var result = false
@@ -573,6 +591,7 @@
         assertEquals(true, result)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testGetCurrentVisibleMediaContentIntent() {
         val clickIntent1 = mock(PendingIntent::class.java)
@@ -619,6 +638,7 @@
         assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
     }
 
+    @Ignore("b/253229241")
     @Test
     fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
         val delta = 0.0001F
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 8c3ae3d..68a5f47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -85,6 +86,10 @@
     private lateinit var fakeAppIconDrawable: Drawable
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
     private lateinit var receiverUiEventLogger: MediaTttReceiverUiEventLogger
+    private lateinit var fakeClock: FakeSystemClock
+    private lateinit var fakeExecutor: FakeExecutor
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
 
     @Before
     fun setUp() {
@@ -99,15 +104,22 @@
         )).thenReturn(applicationInfo)
         context.setMockPackageManager(packageManager)
 
+        fakeClock = FakeSystemClock()
+        fakeExecutor = FakeExecutor(fakeClock)
+
         uiEventLoggerFake = UiEventLoggerFake()
         receiverUiEventLogger = MediaTttReceiverUiEventLogger(uiEventLoggerFake)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         controllerReceiver = MediaTttChipControllerReceiver(
             commandQueue,
             context,
             logger,
             windowManager,
-            FakeExecutor(FakeSystemClock()),
+            fakeExecutor,
             accessibilityManager,
             configurationController,
             powerManager,
@@ -115,6 +127,7 @@
             mediaTttFlags,
             receiverUiEventLogger,
             viewUtil,
+            fakeWakeLockBuilder,
         )
         controllerReceiver.start()
 
@@ -141,6 +154,7 @@
             mediaTttFlags,
             receiverUiEventLogger,
             viewUtil,
+            fakeWakeLockBuilder,
         )
         controllerReceiver.start()
 
@@ -200,6 +214,39 @@
     }
 
     @Test
+    fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() {
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+                routeInfo,
+                null,
+                null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+                routeInfo,
+                null,
+                null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() {
+        commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+                StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+                routeInfo,
+                null,
+                null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
             StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index ad19bc2..4437394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -89,6 +90,8 @@
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var vibratorHelper: VibratorHelper
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var chipbarCoordinator: ChipbarCoordinator
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
@@ -118,6 +121,10 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         uiEventLoggerFake = UiEventLoggerFake()
         uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
 
@@ -134,6 +141,7 @@
                 falsingCollector,
                 viewUtil,
                 vibratorHelper,
+                fakeWakeLockBuilder,
             )
         chipbarCoordinator.start()
 
@@ -472,6 +480,36 @@
     }
 
     @Test
+    fun commandQueueCallback_almostCloseThenFarFromReceiver_wakeLockAcquiredThenReleased() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun commandQueueCallback_FarFromReceiver_wakeLockNeverReleased() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
     fun commandQueueCallback_invalidStateParam_noChipShown() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index f20c6a2..9758842 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
 import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.floating.FloatingTasks
-import java.util.*
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,8 +49,8 @@
 
     @Mock lateinit var context: Context
     @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
-    @Mock lateinit var floatingTasks: FloatingTasks
-    @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+    @Mock lateinit var bubbles: Bubbles
+    @Mock lateinit var optionalBubbles: Optional<Bubbles>
     @Mock lateinit var keyguardManager: KeyguardManager
     @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
     @Mock lateinit var optionalUserManager: Optional<UserManager>
@@ -61,7 +61,7 @@
         MockitoAnnotations.initMocks(this)
 
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
-        whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
         whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
         whenever(userManager.isUserUnlocked).thenReturn(true)
@@ -71,7 +71,7 @@
         return NoteTaskController(
             context = context,
             intentResolver = noteTaskIntentResolver,
-            optionalFloatingTasks = optionalFloatingTasks,
+            optionalBubbles = optionalBubbles,
             optionalKeyguardManager = optionalKeyguardManager,
             optionalUserManager = optionalUserManager,
             isEnabled = isEnabled,
@@ -85,16 +85,16 @@
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
+    fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
-        verify(floatingTasks).showOrSetStashed(notesIntent)
+        verify(bubbles).showAppBubble(notesIntent)
         verify(context, never()).startActivity(notesIntent)
     }
 
@@ -103,17 +103,17 @@
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
-        whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
+    fun handleSystemKey_bubblesIsNull_shouldDoNothing() {
+        whenever(optionalBubbles.orElse(null)).thenReturn(null)
 
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
@@ -123,7 +123,7 @@
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
@@ -133,7 +133,7 @@
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
@@ -143,7 +143,7 @@
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
@@ -151,7 +151,7 @@
         createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
@@ -161,6 +161,6 @@
         createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
 
         verify(context, never()).startActivity(notesIntent)
-        verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+        verify(bubbles, never()).showAppBubble(notesIntent)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index f344c8d..334089c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.floating.FloatingTasks
-import java.util.*
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,20 +43,20 @@
 internal class NoteTaskInitializerTest : SysuiTestCase() {
 
     @Mock lateinit var commandQueue: CommandQueue
-    @Mock lateinit var floatingTasks: FloatingTasks
-    @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+    @Mock lateinit var bubbles: Bubbles
+    @Mock lateinit var optionalBubbles: Optional<Bubbles>
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(optionalFloatingTasks.isPresent).thenReturn(true)
-        whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+        whenever(optionalBubbles.isPresent).thenReturn(true)
+        whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
     }
 
     private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
         return NoteTaskInitializer(
-            optionalFloatingTasks = optionalFloatingTasks,
+            optionalBubbles = optionalBubbles,
             lazyNoteTaskController = mock(),
             commandQueue = commandQueue,
             isEnabled = isEnabled,
@@ -78,8 +78,8 @@
     }
 
     @Test
-    fun initialize_floatingTasksNotPresent_shouldDoNothing() {
-        whenever(optionalFloatingTasks.isPresent).thenReturn(false)
+    fun initialize_bubblesNotPresent_shouldDoNothing() {
+        whenever(optionalBubbles.isPresent).thenReturn(false)
 
         createNoteTaskInitializer().initialize()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 3c867ab..9f28708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -64,7 +64,7 @@
         whenever(brightnessSliderFactory.create(any(), any())).thenReturn(brightnessSlider)
         whenever(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
         whenever(qsPanel.resources).thenReturn(mContext.orCreateTestableResources.resources)
-        whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+        whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
         whenever(qsPanel.setListening(anyBoolean())).then {
             whenever(qsPanel.isListening).thenReturn(it.getArgument(0))
         }
@@ -116,9 +116,9 @@
 
     @Test
     fun testIsBouncerInTransit() {
-        whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true)
+        whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true)
         assertThat(controller.isBouncerInTransit()).isEqualTo(true)
-        whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+        whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
         assertThat(controller.isBouncerInTransit()).isEqualTo(false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 4084cf4..3ae8428 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -42,6 +42,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -52,6 +53,7 @@
 
 import java.util.List;
 
+@Ignore("b/257089187")
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index bd4b94e..52462c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -311,6 +311,37 @@
     }
 
     @Test
+    fun testCallbackCalledOnUserInfoChanged() {
+        tracker.initialize(0)
+        val callback = TestCallback()
+        tracker.addCallback(callback, executor)
+        val profileID = tracker.userId + 10
+
+        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+            val id = invocation.getArgument<Int>(0)
+            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+            val infoProfile = UserInfo(
+                id + 10,
+                "",
+                "",
+                UserInfo.FLAG_MANAGED_PROFILE,
+                UserManager.USER_TYPE_PROFILE_MANAGED
+            )
+            infoProfile.profileGroupId = id
+            listOf(info, infoProfile)
+        }
+
+        val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
+            .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+        tracker.onReceive(context, intent)
+
+        assertThat(callback.calledOnUserChanged).isEqualTo(0)
+        assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+        assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+    }
+
+    @Test
     fun testCallbackRemoved() {
         tracker.initialize(0)
         val newID = 5
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index c98c1f2..fb2046d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -854,7 +854,7 @@
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
                 null);
 
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
     }
 
     @Test
@@ -864,7 +864,7 @@
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
                 null);
 
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index a4a7995..a6c80ab6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@
 
         // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should intercept touch
@@ -165,7 +165,7 @@
 
         // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(false);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@
 
         // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 70cbc64..28bd26a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.util.mockito.eq
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
+import org.json.JSONException
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -238,4 +239,52 @@
         pluginListener.onPluginDisconnected(plugin2)
         assertEquals(1, changeCallCount)
     }
+
+    @Test
+    fun jsonDeserialization_gotExpectedObject() {
+        val expected = ClockRegistry.ClockSetting("ID", 500)
+        val actual = ClockRegistry.ClockSetting.deserialize("""{
+            "clockId":"ID",
+            "_applied_timestamp":500
+        }""")
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonDeserialization_noTimestamp_gotExpectedObject() {
+        val expected = ClockRegistry.ClockSetting("ID", null)
+        val actual = ClockRegistry.ClockSetting.deserialize("{\"clockId\":\"ID\"}")
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
+        val expected = ClockRegistry.ClockSetting("ID", null)
+        val actual = ClockRegistry.ClockSetting.deserialize("""{
+            "clockId":"ID",
+            "_applied_timestamp":null
+        }""")
+        assertEquals(expected, actual)
+    }
+
+    @Test(expected = JSONException::class)
+    fun jsonDeserialization_noId_threwException() {
+        val expected = ClockRegistry.ClockSetting("ID", 500)
+        val actual = ClockRegistry.ClockSetting.deserialize("{\"_applied_timestamp\":500}")
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonSerialization_gotExpectedString() {
+        val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
+        val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", 500))
+        assertEquals(expected, actual)
+    }
+
+    @Test
+    fun jsonSerialization_noTimestamp_gotExpectedString() {
+        val expected = "{\"clockId\":\"ID\"}"
+        val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", null))
+        assertEquals(expected, actual)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index c377990..08933fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -35,7 +35,8 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -135,27 +136,29 @@
     public void testOnSystemBarAttributesChanged() {
         doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+                BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+                TEST_LETTERBOX_DETAILS);
     }
 
     @Test
     public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
         doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+                BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+                TEST_LETTERBOX_DETAILS);
     }
 
     private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                 letterboxDetails);
         waitForIdleSync();
         verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
                 eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
-                eq(requestedVisibilities), eq(packageName), eq(letterboxDetails));
+                eq(requestedVisibleTypes), eq(packageName), eq(letterboxDetails));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index ec5d089..c658593 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -20,6 +20,7 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
 
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
@@ -54,6 +55,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
@@ -88,6 +90,7 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
@@ -173,6 +176,8 @@
     private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
     @Mock
     private ScreenLifecycle mScreenLifecycle;
+    @Mock
+    private AuthController mAuthController;
     @Captor
     private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
     @Captor
@@ -263,8 +268,9 @@
                 mWakeLockBuilder,
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
-                mUserManager, mExecutor, mExecutor,  mFalsingManager, mLockPatternUtils,
-                mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
+                mUserManager, mExecutor, mExecutor, mFalsingManager,
+                mAuthController, mLockPatternUtils, mScreenLifecycle,
+                mKeyguardBypassController, mAccessibilityManager,
                 mFaceHelpMessageDeferral);
         mController.init();
         mController.setIndicationArea(mIndicationArea);
@@ -1374,6 +1380,110 @@
     }
 
 
+    @Test
+    public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
+        createController();
+        fingerprintUnlockIsPossible();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
+        createController();
+        fingerprintUnlockIsNotPossible();
+        onFaceLockoutError("first lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_unlock_unavailable));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
+        onFaceLockoutError("second lockout");
+
+        verifyNoMoreInteractions(mRotateTextViewController);
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
+        createController();
+        fingerprintUnlockIsPossible();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
+    public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
+        createController();
+        fingerprintUnlockIsNotPossible();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_unlock));
+    }
+
+    @Test
+    public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
+    }
+
+    @Test
+    public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
+        createController();
+        onFaceLockoutError("first lockout");
+        clearInvocations(mRotateTextViewController);
+        when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+        mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+        onFaceLockoutError("second lockout");
+
+        verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                mContext.getString(R.string.keyguard_face_unlock_unavailable));
+    }
 
     private void sendUpdateDisclosureBroadcast() {
         mBroadcastReceiver.onReceive(mContext, new Intent());
@@ -1422,4 +1532,33 @@
                     anyObject(), anyBoolean());
         }
     }
+
+    private void verifyIndicationShown(int indicationType, String message) {
+        verify(mRotateTextViewController)
+                .updateIndication(eq(indicationType),
+                        mKeyguardIndicationCaptor.capture(),
+                        eq(true));
+        assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
+                .isEqualTo(message);
+    }
+
+    private void fingerprintUnlockIsNotPossible() {
+        setupFingerprintUnlockPossible(false);
+    }
+
+    private void fingerprintUnlockIsPossible() {
+        setupFingerprintUnlockPossible(true);
+    }
+
+    private void setupFingerprintUnlockPossible(boolean possible) {
+        when(mKeyguardUpdateMonitor
+                .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
+                .thenReturn(possible);
+    }
+
+    private void onFaceLockoutError(String errMsg) {
+        mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
+                errMsg,
+                BiometricSourceType.FACE);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index f96c39f..aa1114b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
@@ -86,6 +87,7 @@
     private val mRemoteInputManager: NotificationRemoteInputManager = mock()
     private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
     private val mHeaderController: NodeController = mock()
+    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
 
     private lateinit var mEntry: NotificationEntry
     private lateinit var mGroupSummary: NotificationEntry
@@ -110,6 +112,7 @@
             mHeadsUpViewBinder,
             mNotificationInterruptStateProvider,
             mRemoteInputManager,
+            mLaunchFullScreenIntentProvider,
             mHeaderController,
             mExecutor)
         mCoordinator.attach(mNotifPipeline)
@@ -242,6 +245,20 @@
     }
 
     @Test
+    fun testOnEntryAdded_shouldFullScreen() {
+        setShouldFullScreen(mEntry)
+        mCollectionListener.onEntryAdded(mEntry)
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+    }
+
+    @Test
+    fun testOnEntryAdded_shouldNotFullScreen() {
+        setShouldFullScreen(mEntry, should = false)
+        mCollectionListener.onEntryAdded(mEntry)
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+    }
+
+    @Test
     fun testPromotesAddedHUN() {
         // GIVEN the current entry should heads up
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
@@ -794,6 +811,11 @@
                 .thenReturn(should)
     }
 
+    private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
+        whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+            .thenReturn(should)
+    }
+
     private fun finishBind(entry: NotificationEntry) {
         verify(mHeadsUpManager, never()).showNotification(entry)
         withArgCaptor<BindCallback> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 137842e..12cc114 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -232,7 +232,6 @@
     @Test
     public void testUserLockedResetEvenWhenNoChildren() {
         mGroupRow.setUserLocked(true);
-        mGroupRow.removeAllChildren();
         mGroupRow.setUserLocked(false);
         assertFalse("The childrencontainer should not be userlocked but is, the state "
                 + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
@@ -240,12 +239,11 @@
 
     @Test
     public void testReinflatedOnDensityChange() {
-        mGroupRow.setUserLocked(true);
-        mGroupRow.removeAllChildren();
-        mGroupRow.setUserLocked(false);
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
-        mGroupRow.setChildrenContainer(mockContainer);
-        mGroupRow.onDensityOrFontScaleChanged();
+        mNotifRow.setChildrenContainer(mockContainer);
+
+        mNotifRow.onDensityOrFontScaleChanged();
+
         verify(mockContainer).reInflateViews(any(), any());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 40aec82..743e7d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -118,7 +118,7 @@
 
     @Test
     fun resetViewStates_expansionChanging_notificationBecomesTransparent() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.25f,
                 expectedAlpha = 0.0f
@@ -127,7 +127,7 @@
 
     @Test
     fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.85f,
                 expectedAlpha = 0.0f
@@ -136,7 +136,7 @@
 
     @Test
     fun resetViewStates_expansionChanging_notificationAlphaUpdated() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(false)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.6f,
                 expectedAlpha = getContentAlpha(0.6f)
@@ -145,7 +145,7 @@
 
     @Test
     fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
-        whenever(mStatusBarKeyguardViewManager.isBouncerInTransit).thenReturn(true)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.95f,
                 expectedAlpha = aboutToShowBouncerProgress(0.95f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 4dea6be..7246116 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -136,28 +136,28 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
-        mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+        mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
     }
 
     @Test
-    public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+    public void onBiometricAuthenticated_fingerprintAndBiometricsDisallowed_showPrimaryBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */))
                 .thenReturn(false);
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
-        verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
 
     @Test
-    public void onBiometricAuthenticated_whenFingerprint_nonStrongBioDisallowed_showBouncer() {
+    public void onBiometricAuthenticated_fingerprint_nonStrongBioDisallowed_showPrimaryBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
                 .thenReturn(false);
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
-        verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
         assertThat(mBiometricUnlockController.getBiometricType())
@@ -207,7 +207,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
@@ -216,7 +216,7 @@
     @Test
     public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -274,7 +274,7 @@
     }
 
     @Test
-    public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showBouncer() {
+    public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showPrimaryBouncer() {
         reset(mUpdateMonitor);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
@@ -285,7 +285,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE, true /* isStrongBiometric */);
 
-        verify(mStatusBarKeyguardViewManager).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
@@ -314,7 +314,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FACE, true /* isStrongBiometric */);
 
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_NONE);
     }
@@ -322,7 +322,7 @@
     @Test
     public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
         when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -342,7 +342,7 @@
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
                 .thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
         // the value of isStrongBiometric doesn't matter here since we only care about the returned
         // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
@@ -369,23 +369,23 @@
     }
 
     @Test
-    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+    public void onUdfpsConsecutivelyFailedThreeTimes_showPrimaryBouncer() {
         // GIVEN UDFPS is supported
         when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
 
         // WHEN udfps fails once - then don't show the bouncer yet
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
 
         // WHEN udfps fails the second time - then don't show the bouncer yet
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+        verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
 
         // WHEN udpfs fails the third time
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN show the bouncer
-        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index e1f20d5..5ebaf69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -29,7 +29,7 @@
 import android.os.PowerManager;
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
 
@@ -177,7 +177,7 @@
         AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
         boolean navbarColorManagedByIme = true;
         int behavior = 456;
-        InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        int requestedVisibleTypes = WindowInsets.Type.systemBars();
         String packageName = "test package name";
         LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
 
@@ -187,7 +187,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
@@ -197,7 +197,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
@@ -209,7 +209,7 @@
         AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
         boolean navbarColorManagedByIme = true;
         int behavior = 456;
-        InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        int requestedVisibleTypes = WindowInsets.Type.systemBars();
         String packageName = "test package name";
         LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
 
@@ -219,7 +219,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index ab209d1..d3b5418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -58,7 +58,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
@@ -86,7 +86,7 @@
     @Mock
     private KeyguardHostViewController mKeyguardHostViewController;
     @Mock
-    private BouncerExpansionCallback mExpansionCallback;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mExpansionCallback;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
@@ -476,7 +476,8 @@
         mBouncer.ensureView();
         mBouncer.setExpansion(0.5f);
 
-        final BouncerExpansionCallback callback = mock(BouncerExpansionCallback.class);
+        final PrimaryBouncerExpansionCallback callback =
+                mock(PrimaryBouncerExpansionCallback.class);
         mBouncer.addBouncerExpansionCallback(callback);
 
         mBouncer.setExpansion(EXPANSION_HIDDEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index fca9771..287ebba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -30,6 +30,7 @@
 import android.view.Display;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import androidx.lifecycle.Observer;
@@ -107,7 +108,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
         assertTrue(mLightsOutNotifController.areLightsOut());
@@ -121,7 +122,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
         assertFalse(mLightsOutNotifController.areLightsOut());
@@ -153,7 +154,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
@@ -174,7 +175,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
@@ -195,7 +196,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 086e5df..b80b825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -92,7 +92,7 @@
 
         iconContainer.calculateIconXTranslations()
         assertEquals(10f, iconState.xTranslation)
-        assertFalse(iconContainer.hasOverflow())
+        assertFalse(iconContainer.areIconsOverflowing())
     }
 
     @Test
@@ -121,7 +121,7 @@
         assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
         assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation)
 
-        assertFalse(iconContainer.hasOverflow())
+        assertFalse(iconContainer.areIconsOverflowing())
     }
 
     @Test
@@ -150,7 +150,7 @@
         assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
         assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
         assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
-        assertTrue(iconContainer.hasOverflow())
+        assertTrue(iconContainer.areIconsOverflowing())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index a5deaa4..df48e1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1150,7 +1150,7 @@
     }
 
     @Test
-    public void testAuthScrim_notifScrimOpaque_whenShadeFullyExpanded() {
+    public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
         // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
         // with the camera app occluding the keyguard)
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -1176,6 +1176,34 @@
         ));
     }
 
+
+    @Test
+    public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
+        // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
+        // with the camera app occluding the keyguard)
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.setClipsQsScrim(false);
+        mScrimController.setRawPanelExpansionFraction(1);
+        // notifications scrim alpha change require calling setQsPosition
+        mScrimController.setQsPosition(0, 300);
+        finishAnimationsImmediately();
+
+        // WHEN the user triggers the auth bouncer
+        mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
+        finishAnimationsImmediately();
+
+        assertEquals("Behind scrim should be opaque",
+                mScrimBehind.getViewAlpha(), 1, 0.0);
+        assertEquals("Notifications scrim should be opaque",
+                mNotificationsScrim.getViewAlpha(), 1, 0.0);
+
+        assertScrimTinted(Map.of(
+                mScrimInFront, true,
+                mScrimBehind, true,
+                mNotificationsScrim, false
+        ));
+    }
+
     @Test
     public void testAuthScrimKeyguard() {
         // GIVEN device is on the keyguard
@@ -1280,7 +1308,7 @@
 
     @Test
     public void qsExpansion_BehindTint_shadeLocked_bouncerActive_usesBouncerProgress() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         // clipping doesn't change tested logic but allows to assert scrims more in line with
         // their expected large screen behaviour
         mScrimController.setClipsQsScrim(false);
@@ -1296,7 +1324,7 @@
 
     @Test
     public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
 
         mScrimController.transitionTo(SHADE_LOCKED);
 
@@ -1312,7 +1340,7 @@
 
     @Test
     public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
         mScrimController.transitionTo(SHADE_LOCKED);
 
@@ -1327,7 +1355,7 @@
 
     @Test
     public void notificationAlpha_unnocclusionAnimating_bouncerActive_usesKeyguardNotifAlpha() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setClipsQsScrim(true);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1349,7 +1377,7 @@
 
     @Test
     public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setUnocclusionAnimationRunning(true);
@@ -1370,7 +1398,7 @@
 
     @Test
     public void notificationAlpha_inKeyguardState_bouncerActive_usesInvertedBouncerInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
         mScrimController.setClipsQsScrim(true);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1390,7 +1418,7 @@
 
     @Test
     public void notificationAlpha_inKeyguardState_bouncerNotActive_usesInvertedShadeInterpolator() {
-        when(mStatusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false);
         mScrimController.setClipsQsScrim(true);
 
         mScrimController.transitionTo(ScrimState.KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9c56c26..6fb6893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -45,7 +45,7 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
@@ -80,7 +80,7 @@
                 layout,
                 StatusBarLocation.HOME,
                 mock(StatusBarPipelineFlags.class),
-                mock(WifiViewModel.class),
+                mock(WifiUiAdapter.class),
                 mock(MobileUiAdapter.class),
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
@@ -124,14 +124,14 @@
                 LinearLayout group,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
                     location,
                     statusBarPipelineFlags,
-                    wifiViewModel,
+                    wifiUiAdapter,
                     mobileUiAdapter,
                     contextProvider,
                     darkIconDispatcher);
@@ -172,7 +172,7 @@
             super(group,
                     StatusBarLocation.HOME,
                     mock(StatusBarPipelineFlags.class),
-                    mock(WifiViewModel.class),
+                    mock(WifiUiAdapter.class),
                     mock(MobileUiAdapter.class),
                     contextProvider);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 7166666..49c3a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -56,8 +56,8 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.data.BouncerView;
 import com.android.systemui.keyguard.data.BouncerViewDelegate;
-import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -105,8 +105,8 @@
     @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
     @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
-    @Mock private KeyguardBouncer mBouncer;
-    @Mock private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
+    @Mock private KeyguardBouncer mPrimaryBouncer;
+    @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
     @Mock private KeyguardMessageArea mKeyguardMessageArea;
     @Mock private ShadeController mShadeController;
     @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@@ -114,13 +114,13 @@
     @Mock private LatencyTracker mLatencyTracker;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
-    @Mock private BouncerInteractor mBouncerInteractor;
+    @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
     private FakeKeyguardStateController mKeyguardStateController =
             spy(new FakeKeyguardStateController());
 
@@ -134,8 +134,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mKeyguardBouncerFactory.create(
-                        any(ViewGroup.class), any(KeyguardBouncer.BouncerExpansionCallback.class)))
-                .thenReturn(mBouncer);
+                any(ViewGroup.class),
+                any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
+                .thenReturn(mPrimaryBouncer);
         when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
         when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
@@ -163,8 +164,8 @@
                         mLatencyTracker,
                         mKeyguardSecurityModel,
                         mFeatureFlags,
-                        mBouncerCallbackInteractor,
-                        mBouncerInteractor,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
                         mBouncerView) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
@@ -181,8 +182,8 @@
                 mNotificationContainer,
                 mBypassController);
         mStatusBarKeyguardViewManager.show(null);
-        ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
-                ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
+        ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+                ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
         verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
                 callbackArgumentCaptor.capture());
         mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
@@ -194,86 +195,86 @@
         Runnable cancelAction = () -> {};
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, false /* afterKeyguardGone */);
-        verify(mBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+        verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
     }
 
     @Test
     public void showBouncer_onlyWhenShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
-        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mBouncer, never()).show(anyBoolean());
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncer, never()).show(anyBoolean());
     }
 
     @Test
     public void showBouncer_notWhenBouncerAlreadyShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        when(mBouncer.isSecure()).thenReturn(true);
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
-        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mBouncer, never()).show(anyBoolean());
+        when(mPrimaryBouncer.isSecure()).thenReturn(true);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncer, never()).show(anyBoolean());
     }
 
     @Test
     public void showBouncer_showsTheBouncer() {
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
-        verify(mBouncer).show(anyBoolean(), eq(true));
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
     }
 
     @Test
     public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
-        when(mBouncer.isShowing()).thenReturn(true);
-        when(mBouncer.isScrimmed()).thenReturn(true);
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncer.isScrimmed()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+        verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
     }
 
     @Test
     public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
         when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
     }
 
     @Test
     public void onPanelExpansionChanged_propagatesToBouncer() {
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncer).setExpansion(eq(0.5f));
     }
 
     @Test
     public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
         mStatusBarKeyguardViewManager.hide(0, 0);
-        when(mBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
 
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
     }
 
     @Test
     public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
         mKeyguardStateController.setCanDismissLockScreen(false);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer).show(eq(false), eq(false));
+        verify(mPrimaryBouncer).show(eq(false), eq(false));
 
         // But not when it's already visible
-        reset(mBouncer);
-        when(mBouncer.isShowing()).thenReturn(true);
+        reset(mPrimaryBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer, never()).show(eq(false), eq(false));
+        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
 
         // Or animating away
-        reset(mBouncer);
-        when(mBouncer.isAnimatingAway()).thenReturn(true);
+        reset(mPrimaryBouncer);
+        when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer, never()).show(eq(false), eq(false));
+        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
     }
 
     @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
         mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mBouncer, never()).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
     }
 
     @Test
@@ -285,7 +286,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
     }
 
     @Test
@@ -302,18 +303,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
-        when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(
-                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
-                        /* expanded= */ true,
-                        /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
     }
 
     @Test
@@ -324,7 +314,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
     }
 
     @Test
@@ -332,7 +322,7 @@
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
         verify(mCentralSurfaces).animateKeyguardUnoccluding();
 
-        when(mBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
         clearInvocations(mCentralSurfaces);
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
         verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
@@ -361,7 +351,6 @@
 
     @Test
     public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
-        when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
         mStatusBarKeyguardViewManager.show(null);
 
         mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
@@ -384,7 +373,7 @@
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, true /* afterKeyguardGone */);
 
-        when(mBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(true);
         mStatusBarKeyguardViewManager.hide(0, 30);
         verify(action, never()).onDismiss();
@@ -398,7 +387,7 @@
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, true /* afterKeyguardGone */);
 
-        when(mBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(true);
 
         verify(action, never()).onDismiss();
@@ -419,9 +408,9 @@
 
     @Test
     public void testShowing_whenAlternateAuthShowing() {
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         assertTrue(
                 "Is showing not accurate when alternative auth showing",
                 mStatusBarKeyguardViewManager.isBouncerShowing());
@@ -429,93 +418,93 @@
 
     @Test
     public void testWillBeShowing_whenAlternateAuthShowing() {
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         assertTrue(
                 "Is or will be showing not accurate when alternative auth showing",
-                mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
     }
 
     @Test
-    public void testHideAltAuth_onShowBouncer() {
+    public void testHideAlternateBouncer_onShowBouncer() {
         // GIVEN alt auth is showing
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
-        reset(mAlternateAuthInterceptor);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        reset(mAlternateBouncer);
 
         // WHEN showBouncer is called
-        mStatusBarKeyguardViewManager.showBouncer(true);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
 
         // THEN alt bouncer should be hidden
-        verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
+        verify(mAlternateBouncer).hideAlternateBouncer();
     }
 
     @Test
     public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
-        when(mBouncer.isShowing()).thenReturn(false);
-        when(mBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
 
         assertTrue(
                 "Is or will be showing should be true when bouncer is in transit",
-                mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing());
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
     }
 
     @Test
     public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
         // GIVEN alt auth exists, unlocking with biometric isn't allowed
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
                 .thenReturn(false);
 
         // WHEN showGenericBouncer is called
         final boolean scrimmed = true;
-        mStatusBarKeyguardViewManager.showGenericBouncer(scrimmed);
+        mStatusBarKeyguardViewManager.showBouncer(scrimmed);
 
         // THEN regular bouncer is shown
-        verify(mBouncer).show(anyBoolean(), eq(scrimmed));
-        verify(mAlternateAuthInterceptor, never()).showAlternateAuthBouncer();
+        verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+        verify(mAlternateBouncer, never()).showAlternateBouncer();
     }
 
     @Test
-    public void testShowAltAuth_unlockingWithBiometricAllowed() {
+    public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
         // GIVEN alt auth exists, unlocking with biometric is allowed
-        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        when(mBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
 
         // WHEN showGenericBouncer is called
-        mStatusBarKeyguardViewManager.showGenericBouncer(true);
+        mStatusBarKeyguardViewManager.showBouncer(true);
 
         // THEN alt auth bouncer is shown
-        verify(mAlternateAuthInterceptor).showAlternateAuthBouncer();
-        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mAlternateBouncer).showAlternateBouncer();
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
     }
 
     @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
-        verify(mBouncer).updateResources();
+        verify(mPrimaryBouncer).updateResources();
     }
 
     @Test
     public void updateKeyguardPosition_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
 
-        verify(mBouncer).updateKeyguardPosition(1.0f);
+        verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
     }
 
     @Test
     public void testIsBouncerInTransit() {
-        when(mBouncer.inTransit()).thenReturn(true);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isTrue();
-        when(mBouncer.inTransit()).thenReturn(false);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
-        mBouncer = null;
-        Truth.assertThat(mStatusBarKeyguardViewManager.isBouncerInTransit()).isFalse();
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+        when(mPrimaryBouncer.inTransit()).thenReturn(false);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+        mPrimaryBouncer = null;
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
     }
 
     private static ShadeExpansionChangeEvent expansionEvent(
@@ -546,7 +535,7 @@
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
                 mOnBackInvokedCallback.capture());
 
-        when(mBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
         when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
         /* invoke the back callback directly */
         mOnBackInvokedCallback.getValue().onBackInvoked();
@@ -579,6 +568,6 @@
     public void flag_off_DoesNotCallBouncerInteractor() {
         when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(false);
-        verify(mBouncerInteractor, never()).hide();
+        verify(mPrimaryBouncerInteractor, never()).hide();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index c409857..ce54d78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -67,8 +67,8 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -123,8 +123,6 @@
     @Mock
     private ShadeControllerImpl mShadeController;
     @Mock
-    private NotifPipeline mNotifPipeline;
-    @Mock
     private NotificationVisibilityProvider mVisibilityProvider;
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -197,7 +195,6 @@
                         getContext(),
                         mHandler,
                         mUiBgExecutor,
-                        mNotifPipeline,
                         mVisibilityProvider,
                         headsUpManager,
                         mActivityStarter,
@@ -222,7 +219,8 @@
                         mock(NotificationPresenter.class),
                         mock(NotificationPanelViewController.class),
                         mActivityLaunchAnimator,
-                        notificationAnimationProvider
+                        notificationAnimationProvider,
+                        mock(LaunchFullScreenIntentProvider.class)
                 );
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
@@ -384,11 +382,9 @@
         NotificationEntry entry = mock(NotificationEntry.class);
         when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
         when(entry.getSbn()).thenReturn(sbn);
-        when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry)))
-                .thenReturn(true);
 
         // WHEN
-        mNotificationActivityStarter.handleFullScreenIntent(entry);
+        mNotificationActivityStarter.launchFullScreenIntent(entry);
 
         // THEN display should try wake up for the full screen intent
         verify(mCentralSurfaces).wakeUpForFullScreenIntent();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index c3a7e65..613238f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -99,6 +99,6 @@
         mRemoteInputCallback.onLockedRemoteInput(
                 mock(ExpandableNotificationRow.class), mock(View.class));
 
-        verify(mStatusBarKeyguardViewManager).showGenericBouncer(true);
+        verify(mStatusBarKeyguardViewManager).showBouncer(true);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index 9957c2a..4d79a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -3,12 +3,9 @@
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.view.Display
-import android.view.InsetsVisibilities
+import android.view.WindowInsets
 import android.view.WindowInsetsController
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
-import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
-import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.*
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
@@ -27,8 +24,8 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -88,7 +85,7 @@
         changeSysBarAttrs(TEST_APPEARANCE)
 
         verify(statusBarStateController)
-            .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+            .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -97,7 +94,7 @@
 
         verify(statusBarStateController)
             .setSystemBarAttributes(
-                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -130,7 +127,7 @@
 
         verify(statusBarStateController)
             .setSystemBarAttributes(
-                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -197,7 +194,7 @@
             appearanceRegions,
             /* navbarColorManagedByIme= */ false,
             WindowInsetsController.BEHAVIOR_DEFAULT,
-            InsetsVisibilities(),
+            WindowInsets.Type.defaultVisible(),
             "package name",
             letterboxDetails)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index c584109..37457b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -40,6 +39,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -70,7 +70,7 @@
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
-    private lateinit var viewModel: WifiViewModel
+    private lateinit var viewModel: LocationBasedWifiViewModel
     private lateinit var scope: CoroutineScope
     private lateinit var airplaneModeViewModel: AirplaneModeViewModel
 
@@ -105,23 +105,19 @@
             scope,
             statusBarPipelineFlags,
             wifiConstants,
-        )
+        ).home
     }
 
     @Test
     fun constructAndBind_hasCorrectSlot() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, "slotName", viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
 
         assertThat(view.slot).isEqualTo("slotName")
     }
 
     @Test
     fun getVisibleState_icon_returnsIcon() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_ICON, /* animate= */ false)
 
@@ -130,9 +126,7 @@
 
     @Test
     fun getVisibleState_dot_returnsDot() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_DOT, /* animate= */ false)
 
@@ -141,9 +135,7 @@
 
     @Test
     fun getVisibleState_hidden_returnsHidden() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
 
@@ -155,9 +147,7 @@
 
     @Test
     fun setVisibleState_icon_iconShownDotHidden() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_ICON, /* animate= */ false)
 
@@ -172,9 +162,7 @@
 
     @Test
     fun setVisibleState_dot_iconHiddenDotShown() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_DOT, /* animate= */ false)
 
@@ -189,9 +177,7 @@
 
     @Test
     fun setVisibleState_hidden_iconAndDotHidden() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
 
@@ -211,9 +197,7 @@
             WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
         )
 
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
@@ -230,9 +214,7 @@
             WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
         )
 
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 4f1fb02..26df03f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +26,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.net.TetheringManager;
@@ -36,6 +38,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 
@@ -96,6 +99,9 @@
         }).when(mWifiManager).registerSoftApCallback(any(Executor.class),
                 any(WifiManager.SoftApCallback.class));
 
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_show_wifi_tethering, true);
+
         Handler handler = new Handler(mLooper.getLooper());
 
         mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
@@ -176,4 +182,18 @@
 
         verify(mCallback1).onHotspotAvailabilityChanged(false);
     }
+
+    @Test
+    public void testHotspotSupported_resource_false() {
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_show_wifi_tethering, false);
+
+        Handler handler = new Handler(mLooper.getLooper());
+
+        HotspotController controller =
+                new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+
+        verifyNoMoreInteractions(mTetheringManager);
+        assertFalse(controller.isHotspotSupported());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 91b5c35..9dea48e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -35,6 +35,8 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -53,6 +55,9 @@
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var fakeExecutor: FakeExecutor
 
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
+
     @Mock
     private lateinit var logger: TemporaryViewLogger
     @Mock
@@ -74,6 +79,10 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         underTest = TestController(
                 context,
                 logger,
@@ -82,7 +91,9 @@
                 accessibilityManager,
                 configurationController,
                 powerManager,
+                fakeWakeLockBuilder,
         )
+        underTest.start()
     }
 
     @Test
@@ -112,25 +123,33 @@
     }
 
     @Test
-    fun displayView_screenOff_screenWakes() {
-        whenever(powerManager.isScreenOn).thenReturn(false)
-
+    fun displayView_screenOff_wakeLockAcquired() {
         underTest.displayView(getState())
 
-        verify(powerManager).wakeUp(any(), any(), any())
+        assertThat(fakeWakeLock.isHeld).isTrue()
     }
 
     @Test
-    fun displayView_screenAlreadyOn_screenNotWoken() {
+    fun displayView_screenAlreadyOn_wakeLockNotAcquired() {
         whenever(powerManager.isScreenOn).thenReturn(true)
 
         underTest.displayView(getState())
 
-        verify(powerManager, never()).wakeUp(any(), any(), any())
+        assertThat(fakeWakeLock.isHeld).isFalse()
     }
 
     @Test
-    fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
+    fun displayView_screenOff_wakeLockCanBeReleasedAfterTimeOut() {
+        underTest.displayView(getState())
+        assertThat(fakeWakeLock.isHeld).isTrue()
+
+        fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+        assertThat(fakeWakeLock.isHeld).isFalse()
+    }
+
+    @Test
+    fun displayView_twice_viewNotAddedTwice() {
         underTest.displayView(getState())
         reset(windowManager)
 
@@ -269,6 +288,7 @@
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
         powerManager: PowerManager,
+        wakeLockBuilder: WakeLock.Builder,
     ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
         context,
         logger,
@@ -278,13 +298,12 @@
         configurationController,
         powerManager,
         R.layout.chipbar,
+        wakeLockBuilder,
     ) {
         var mostRecentViewInfo: ViewInfo? = null
 
         override val windowLayoutParams = commonWindowLayoutParams
 
-        override fun start() {}
-
         override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
             mostRecentViewInfo = newInfo
         }
@@ -292,6 +311,8 @@
         override fun getTouchableRegion(view: View, outRect: Rect) {
             outRect.setEmpty()
         }
+
+        override fun start() {}
     }
 
     inner class ViewInfo(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index f643973..8e37aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -69,6 +70,8 @@
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var vibratorHelper: VibratorHelper
+    private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+    private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var fakeClock: FakeSystemClock
     private lateinit var fakeExecutor: FakeExecutor
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -81,6 +84,10 @@
         fakeClock = FakeSystemClock()
         fakeExecutor = FakeExecutor(fakeClock)
 
+        fakeWakeLock = WakeLockFake()
+        fakeWakeLockBuilder = WakeLockFake.Builder(context)
+        fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
         uiEventLoggerFake = UiEventLoggerFake()
 
         underTest =
@@ -96,6 +103,7 @@
                 falsingCollector,
                 viewUtil,
                 vibratorHelper,
+                fakeWakeLockBuilder,
             )
         underTest.start()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 574f70e..beedf9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
 
 /** A fake implementation of [ChipbarCoordinator] for testing. */
 class FakeChipbarCoordinator(
@@ -41,6 +42,7 @@
     falsingCollector: FalsingCollector,
     viewUtil: ViewUtil,
     vibratorHelper: VibratorHelper,
+    wakeLockBuilder: WakeLock.Builder,
 ) :
     ChipbarCoordinator(
         context,
@@ -54,6 +56,7 @@
         falsingCollector,
         viewUtil,
         vibratorHelper,
+        wakeLockBuilder,
     ) {
     override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
new file mode 100644
index 0000000..51afbcb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.user
+
+import android.app.Dialog
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class CreateUserActivityTest : SysuiTestCase() {
+    open class CreateUserActivityTestable :
+        CreateUserActivity(
+            /* userCreator = */ mock(),
+            /* editUserInfoController = */ mock {
+                val dialog: Dialog = mock()
+                whenever(
+                        createDialog(
+                            /* activity = */ nullable(),
+                            /* activityStarter = */ nullable(),
+                            /* oldUserIcon = */ nullable(),
+                            /* defaultUserName = */ nullable(),
+                            /* title = */ nullable(),
+                            /* successCallback = */ nullable(),
+                            /* cancelCallback = */ nullable()
+                        )
+                    )
+                    .thenReturn(dialog)
+            },
+            /* activityManager = */ mock(),
+            /* activityStarter = */ mock(),
+        )
+
+    @get:Rule val activityRule = ActivityScenarioRule(CreateUserActivityTestable::class.java)
+
+    @Test
+    fun onBackPressed_finishActivity() {
+        activityRule.scenario.onActivity { activity ->
+            assertThat(activity.isFinishing).isFalse()
+
+            activity.onBackPressed()
+
+            assertThat(activity.isFinishing).isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index 525d837..7c7f0e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -145,6 +145,25 @@
         assertThat(userInfos).isEqualTo(expectedUsers)
     }
 
+    @Test
+    fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUserInfo: UserInfo? = null
+        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 0,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id == 0)
+        setUpUsers(
+            count = 2,
+            selectedIndex = 1,
+        )
+        tracker.onProfileChanged()
+        assertThat(selectedUserInfo?.id == 1)
+    }
+
     private fun setUpUsers(
         count: Int,
         isLastGuestUser: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index e496521..fb781e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -23,6 +23,8 @@
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -55,6 +57,8 @@
     @Mock private lateinit var dismissDialog: () -> Unit
     @Mock private lateinit var selectUser: (Int) -> Unit
     @Mock private lateinit var switchUser: (Int) -> Unit
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
     private lateinit var underTest: GuestUserInteractor
 
@@ -87,10 +91,18 @@
                         repository = repository,
                     ),
                 uiEventLogger = uiEventLogger,
+                resumeSessionReceiver = resumeSessionReceiver,
+                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
             )
     }
 
     @Test
+    fun `registers broadcast receivers`() {
+        verify(resumeSessionReceiver).register()
+        verify(resetOrExitSessionReceiver).register()
+    }
+
+    @Test
     fun `onDeviceBootCompleted - allowed to add - create guest`() =
         runBlocking(IMMEDIATE) {
             setAllowedToAdd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 1680c36c..58f5531 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -21,6 +21,8 @@
 import android.app.admin.DevicePolicyManager
 import android.os.UserManager
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -48,6 +50,8 @@
     @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
     @Mock protected lateinit var uiEventLogger: UiEventLogger
     @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
     protected lateinit var underTest: UserInteractor
 
@@ -107,6 +111,8 @@
                         devicePolicyManager = devicePolicyManager,
                         refreshUsersScheduler = refreshUsersScheduler,
                         uiEventLogger = uiEventLogger,
+                        resumeSessionReceiver = resumeSessionReceiver,
+                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     )
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c12a868..116023a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,8 @@
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
@@ -69,6 +71,8 @@
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
     private lateinit var underTest: UserSwitcherViewModel
 
@@ -104,6 +108,8 @@
                 devicePolicyManager = devicePolicyManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 uiEventLogger = uiEventLogger,
+                resumeSessionReceiver = resumeSessionReceiver,
+                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index fe01f84..6e109ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -42,7 +42,9 @@
 
     @Before
     public void setUp() {
-        mInner = WakeLock.createPartialInner(mContext, WakeLockTest.class.getName());
+        mInner = WakeLock.createWakeLockInner(mContext,
+                WakeLockTest.class.getName(),
+                PowerManager.PARTIAL_WAKE_LOCK);
         mWakeLock = WakeLock.wrap(mInner, 20000);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index fa7ebf6a..d42f757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,6 +77,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.colorextraction.ColorExtractor;
@@ -86,6 +87,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -156,6 +158,7 @@
 import java.util.List;
 import java.util.Optional;
 
+@FlakyTest
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -394,6 +397,7 @@
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
+                mock(FeatureFlags.class),
                 syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
index 34c83bd..d47e88f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -39,6 +39,7 @@
     private boolean mShouldEnforceBouncer;
     private boolean mIsReportingEnabled;
     private boolean mIsFalseRobustTap;
+    private boolean mIsFalseLongTap;
     private boolean mDestroyed;
     private boolean mIsProximityNear;
 
@@ -87,6 +88,10 @@
         mIsProximityNear = proxNear;
     }
 
+    public void setFalseLongTap(boolean falseLongTap) {
+        mIsFalseLongTap = falseLongTap;
+    }
+
     @Override
     public boolean isSimpleTap() {
         checkDestroyed();
@@ -100,6 +105,12 @@
     }
 
     @Override
+    public boolean isFalseLongTap(int penalty) {
+        checkDestroyed();
+        return mIsFalseLongTap;
+    }
+
+    @Override
     public boolean isFalseDoubleTap() {
         checkDestroyed();
         return mIsFalseDoubleTap;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index a60b773..a35427f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -21,14 +21,14 @@
 class FakeFeatureFlags : FeatureFlags {
     private val booleanFlags = mutableMapOf<Int, Boolean>()
     private val stringFlags = mutableMapOf<Int, String>()
+    private val intFlags = mutableMapOf<Int, Int>()
     private val knownFlagNames = mutableMapOf<Int, String>()
     private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
     private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
 
     init {
-        Flags.flagFields.forEach { field ->
-            val flag: Flag<*> = field.get(null) as Flag<*>
-            knownFlagNames[flag.id] = field.name
+        Flags.flagFields.forEach { entry: Map.Entry<String, Flag<*>> ->
+            knownFlagNames[entry.value.id] = entry.key
         }
     }
 
@@ -95,6 +95,10 @@
 
     override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
 
+    override fun getInt(flag: IntFlag): Int = requireIntValue(flag.id)
+
+    override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.id)
+
     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
         flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
         listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
@@ -118,11 +122,16 @@
 
     private fun requireBooleanValue(flagId: Int): Boolean {
         return booleanFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
+            ?: error("Flag ${flagName(flagId)} was accessed as boolean but not specified.")
     }
 
     private fun requireStringValue(flagId: Int): String {
         return stringFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed but not specified.")
+            ?: error("Flag ${flagName(flagId)} was accessed as string but not specified.")
+    }
+
+    private fun requireIntValue(flagId: Int): Int {
+        return intFlags[flagId]
+            ?: error("Flag ${flagName(flagId)} was accessed as int but not specified.")
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 11178db..6f70f0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import kotlinx.coroutines.flow.Flow
@@ -52,6 +53,14 @@
     private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
     override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
 
+    private val _isUdfpsSupported = MutableStateFlow(false)
+    
+    private val _isBouncerShowing = MutableStateFlow(false)
+    override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
     override fun isKeyguardShowing(): Boolean {
         return _isKeyguardShowing.value
     }
@@ -79,4 +88,8 @@
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
+
+    override fun isUdfpsSupported(): Boolean {
+        return _isUdfpsSupported.value
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 9726bf8..a7eadba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -68,4 +68,8 @@
 
         callbacks.forEach { it.onUserChanged(_userId, userContext) }
     }
+
+    fun onProfileChanged() {
+        callbacks.forEach { it.onProfilesChanged(_userProfiles) }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 23c7a61..2d6d29a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -62,7 +62,11 @@
     }
 
     @Override
-    public void setSignalIcon(String slot, WifiIconState state) {
+    public void setWifiIcon(String slot, WifiIconState state) {
+    }
+
+    @Override
+    public void setNewWifiIcon() {
     }
 
     @Override
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fa52ac9..8055afc 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,9 +26,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,13 +45,19 @@
 import android.os.SystemProperties;
 import android.util.PackageUtils;
 import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 
+import libcore.util.HexEncoding;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -151,6 +163,117 @@
                     return 0;
                 }
 
+                private void printPackageMeasurements(PackageInfo packageInfo,
+                                                      final PrintWriter pw) {
+                    pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
+                    // TODO: To be moved to somewhere more suitable
+                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                    ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+                            ApkSignatureVerifier.verifySignaturesInternal(input,
+                                    packageInfo.applicationInfo.sourceDir,
+                                    SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
+                    if (parseResult.isError()) {
+                        pw.println("ERROR: Failed to compute package content digest for "
+                                + packageInfo.applicationInfo.sourceDir + "due to: "
+                                + parseResult.getErrorMessage());
+                    } else {
+                        ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
+                                parseResult.getResult();
+                        Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
+                        for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+                            Integer algorithmId = entry.getKey();
+                            byte[] cDigest = entry.getValue();
+                            //pw.print("Content digest algorithm: ");
+                            switch (algorithmId) {
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+                                    pw.print("CHUNKED_SHA256:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+                                    pw.print("CHUNKED_SHA512:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                                    pw.print("VERITY_CHUNKED_SHA256:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+                                    pw.print("SHA256:");
+                                    break;
+                                default:
+                                    pw.print("UNKNOWN:");
+                            }
+                            pw.print(HexEncoding.encodeToString(cDigest, false));
+                        }
+                    }
+                }
+
+                private void printPackageInstallationInfo(PackageInfo packageInfo,
+                                                          final PrintWriter pw) {
+                    pw.println("--- Package Installation Info ---");
+                    pw.println("Current install location: "
+                            + packageInfo.applicationInfo.sourceDir);
+                    if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
+                        String origPackageFilepath = getOriginalPreinstalledLocation(
+                                packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+                        pw.println("|--> Package pre-installed location: " + origPackageFilepath);
+                        String digest = mBinaryHashes.get(origPackageFilepath);
+                        if (digest == null) {
+                            pw.println("ERROR finding SHA256-digest from cache...");
+                        } else {
+                            pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+                        }
+                    }
+                    pw.println("First install time (ms): " + packageInfo.firstInstallTime);
+                    pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+                    boolean isPreloaded = (packageInfo.firstInstallTime
+                            == packageInfo.lastUpdateTime);
+                    pw.println("Is preloaded: " + isPreloaded);
+
+                    InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+                            packageInfo.packageName);
+                    if (installSourceInfo == null) {
+                        pw.println("ERROR: Unable to obtain installSourceInfo of "
+                                + packageInfo.packageName);
+                    } else {
+                        pw.println("Installation initiated by: "
+                                + installSourceInfo.getInitiatingPackageName());
+                        pw.println("Installation done by: "
+                                + installSourceInfo.getInstallingPackageName());
+                        pw.println("Installation originating from: "
+                                + installSourceInfo.getOriginatingPackageName());
+                    }
+
+                    if (packageInfo.isApex) {
+                        pw.println("Is an active APEX: " + packageInfo.isActiveApex);
+                    }
+                }
+
+                private void printPackageSignerDetails(SigningInfo signerInfo,
+                                                       final PrintWriter pw) {
+                    if (signerInfo == null) {
+                        pw.println("ERROR: Package's signingInfo is null.");
+                        return;
+                    }
+                    pw.println("--- Package Signer Info ---");
+                    pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
+                    Signature[] packageSigners = signerInfo.getApkContentsSigners();
+                    for (Signature packageSigner : packageSigners) {
+                        byte[] packageSignerDigestBytes =
+                                PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+                        String packageSignerDigestHextring =
+                                HexEncoding.encodeToString(packageSignerDigestBytes, false);
+                        pw.println("Signer cert's SHA256-digest: " + packageSignerDigestHextring);
+                        try {
+                            PublicKey publicKey = packageSigner.getPublicKey();
+                            pw.println("Signing key algorithm: " + publicKey.getAlgorithm());
+                        } catch (CertificateException e) {
+                            Slog.e(TAG,
+                                    "Failed to obtain public key of signer for cert with hash: "
+                                    + packageSignerDigestHextring);
+                            e.printStackTrace();
+                        }
+                    }
+
+                }
+
                 private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
                     pw.println("--- Module Details ---");
                     pw.println("Module name: " + moduleInfo.getName());
@@ -190,28 +313,35 @@
                         return -1;
                     }
 
-                    pw.println("APEX Info:");
+                    if (!verbose) {
+                        pw.println("APEX Info [Format: package_name,package_version,"
+                                + "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
                     for (PackageInfo packageInfo : getInstalledApexs()) {
+                        if (verbose) {
+                            pw.println("APEX Info [Format: package_name,package_version,"
+                                    + "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
                         String packageName = packageInfo.packageName;
-                        pw.println(packageName + ";"
-                                + packageInfo.getLongVersionCode() + ":"
-                                + mBinaryHashes.get(packageName).toLowerCase());
+                        pw.print(packageName + ","
+                                + packageInfo.getLongVersionCode() + ",");
+                        printPackageMeasurements(packageInfo, pw);
+                        pw.print("\n");
 
                         if (verbose) {
-                            pw.println("Install location: "
-                                    + packageInfo.applicationInfo.sourceDir);
-                            pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
-
                             ModuleInfo moduleInfo;
                             try {
                                 moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
+                                pw.println("Is a module: true");
+                                printModuleDetails(moduleInfo, pw);
                             } catch (PackageManager.NameNotFoundException e) {
-                                pw.println("Is A Module: False");
-                                pw.println("");
-                                continue;
+                                pw.println("Is a module: false");
                             }
-                            pw.println("Is A Module: True");
-                            printModuleDetails(moduleInfo, pw);
+
+                            printPackageInstallationInfo(packageInfo, pw);
+                            printPackageSignerDetails(packageInfo.signingInfo, pw);
                             pw.println("");
                         }
                     }
@@ -250,25 +380,37 @@
                         return -1;
                     }
 
-                    pw.println("Module Info:");
+                    if (!verbose) {
+                        pw.println("Module Info [Format: package_name,package_version,"
+                                + "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
                     for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
                         String packageName = module.getPackageName();
+                        if (verbose) {
+                            pw.println("Module Info [Format: package_name,package_version,"
+                                    + "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
                         try {
                             PackageInfo packageInfo = pm.getPackageInfo(packageName,
-                                    PackageManager.MATCH_APEX);
-                            pw.println(packageInfo.packageName + ";"
-                                    + packageInfo.getLongVersionCode() + ":"
-                                    + mBinaryHashes.get(packageName).toLowerCase());
+                                    PackageManager.MATCH_APEX
+                                            | PackageManager.GET_SIGNING_CERTIFICATES);
+                            //pw.print("package:");
+                            pw.print(packageInfo.packageName + ",");
+                            pw.print(packageInfo.getLongVersionCode() + ",");
+                            printPackageMeasurements(packageInfo, pw);
+                            pw.print("\n");
 
                             if (verbose) {
-                                pw.println("Install location: "
-                                        + packageInfo.applicationInfo.sourceDir);
                                 printModuleDetails(module, pw);
+                                printPackageInstallationInfo(packageInfo, pw);
+                                printPackageSignerDetails(packageInfo.signingInfo, pw);
                                 pw.println("");
                             }
                         } catch (PackageManager.NameNotFoundException e) {
                             pw.println(packageName
-                                    + ";ERROR:Unable to find PackageInfo for this module.");
+                                    + ",ERROR:Unable to find PackageInfo for this module.");
                             if (verbose) {
                                 printModuleDetails(module, pw);
                                 pw.println("");
@@ -468,7 +610,8 @@
             return results;
         }
         List<PackageInfo> allPackages = pm.getInstalledPackages(
-                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX
+                        | PackageManager.GET_SIGNING_CERTIFICATES));
         if (allPackages == null) {
             Slog.e(TAG, "Error obtaining installed packages (including APEX)");
             return results;
@@ -478,6 +621,21 @@
         return results;
     }
 
+    @Nullable
+    private InstallSourceInfo getInstallSourceInfo(String packageName) {
+        PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+            return null;
+        }
+        try {
+            return pm.getInstallSourceInfo(packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
 
     /**
      * Updates the internal data structure with the most current APEX measurements.
@@ -539,6 +697,14 @@
         return true;
     }
 
+    private String getOriginalPreinstalledLocation(String packageName,
+                                                   String currentInstalledLocation) {
+        if (currentInstalledLocation.contains("/decompressed/")) {
+            return "/system/apex/" + packageName + ".capex";
+        }
+        return "/system/apex" + packageName + "apex";
+    }
+
     private void doFreshBinaryMeasurements() {
         PackageManager pm = mContext.getPackageManager();
         Slog.d(TAG, "Obtained package manager");
@@ -567,6 +733,27 @@
             mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
         }
 
+        for (PackageInfo packageInfo : getInstalledApexs()) {
+            ApplicationInfo appInfo = packageInfo.applicationInfo;
+            if (!appInfo.sourceDir.startsWith("/data/")) {
+                continue;
+            }
+            String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
+                                                                     appInfo.sourceDir);
+
+            // compute SHA256 for the orig /system APEXs
+            String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
+                                                                               largeFileBuffer);
+            if (sha256digest == null) {
+                Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
+                                          origInstallPath));
+                mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
+            } else {
+                mBinaryHashes.put(origInstallPath, sha256digest);
+            }
+            // there'd be no entry into mBinaryLastUpdateTimes
+        }
+
         // Next, get all installed modules from PackageManager - skip over those APEXs we've
         // processed above
         for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c1c9fbb..b56d1fc 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -231,7 +231,12 @@
             String namespaceToReset = namespaceIt.next();
             Properties properties = new Properties.Builder(namespaceToReset).build();
             try {
-                DeviceConfig.setProperties(properties);
+                if (!DeviceConfig.setProperties(properties)) {
+                    logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+                            + namespaceToReset
+                            + ". Running `device_config get_sync_disabled_for_tests` will confirm"
+                            + " if config-bulk-update is enabled.");
+                }
             } catch (DeviceConfig.BadConfigException exception) {
                 logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
                         + " is already banned, skip reset.");
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index fcfee5b..6b6351f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -802,12 +802,20 @@
                     break;
                 }
                 case H_CLOUD_MEDIA_PROVIDER_CHANGED: {
-                    final Object listener = msg.obj;
-                    if (listener instanceof StorageManagerInternal.CloudProviderChangeListener) {
-                        notifyCloudMediaProviderChangedAsync(
-                                (StorageManagerInternal.CloudProviderChangeListener) listener);
+                    // We send this message in two cases:
+                    // 1. After the cloud provider has been set/updated for a user.
+                    //    In this case Message's #arg1 is set to UserId, and #obj is set to the
+                    //    authority of the new cloud provider.
+                    // 2. After a new CloudProviderChangeListener is registered.
+                    //    In this case Message's #obj is set to the CloudProviderChangeListener.
+                    if (msg.obj instanceof StorageManagerInternal.CloudProviderChangeListener) {
+                        final StorageManagerInternal.CloudProviderChangeListener listener =
+                                (StorageManagerInternal.CloudProviderChangeListener) msg.obj;
+                        notifyCloudMediaProviderChangedAsync(listener);
                     } else {
-                        onCloudMediaProviderChangedAsync(msg.arg1);
+                        final int userId = msg.arg1;
+                        final String authority = (String) msg.obj;
+                        onCloudMediaProviderChangedAsync(userId, authority);
                     }
                     break;
                 }
@@ -1686,17 +1694,15 @@
             @NonNull StorageManagerInternal.CloudProviderChangeListener listener) {
         synchronized (mCloudMediaProviders) {
             for (int i = mCloudMediaProviders.size() - 1; i >= 0; --i) {
-                listener.onCloudProviderChanged(
-                        mCloudMediaProviders.keyAt(i), mCloudMediaProviders.valueAt(i));
+                final int userId = mCloudMediaProviders.keyAt(i);
+                final String authority = mCloudMediaProviders.valueAt(i);
+                listener.onCloudProviderChanged(userId, authority);
             }
         }
     }
 
-    private void onCloudMediaProviderChangedAsync(int userId) {
-        final String authority;
-        synchronized (mCloudMediaProviders) {
-            authority = mCloudMediaProviders.get(userId);
-        }
+    private void onCloudMediaProviderChangedAsync(
+            @UserIdInt int userId, @Nullable String authority) {
         for (StorageManagerInternal.CloudProviderChangeListener listener :
                 mStorageManagerInternal.mCloudProviderChangeListeners) {
             listener.onCloudProviderChanged(userId, authority);
@@ -4831,7 +4837,7 @@
         public void registerCloudProviderChangeListener(
                 @NonNull StorageManagerInternal.CloudProviderChangeListener listener) {
             mCloudProviderChangeListeners.add(listener);
-            mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener);
+            mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener).sendToTarget();
         }
     }
 }
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 83d86cd..953e850 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -371,6 +371,7 @@
             // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
             // called onUserSwitching(), so calling it before onUserStarting() make it more
             // consistent with that
+            EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
             onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
         }
         onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
@@ -381,14 +382,30 @@
      *
      * <p><b>NOTE: </b>this method should only be called when a user that is already running become
      * visible; if the user is starting visible, callers should call
-     * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+     * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
      */
     public void onUserVisible(@UserIdInt int userId) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
         onUser(USER_VISIBLE, userId);
     }
 
     /**
+     * Updates the visibility of the system user.
+     *
+     * <p>Since the system user never stops, this method must be called when it's switched from / to
+     * foreground.
+     */
+    public void onSystemUserVisibilityChanged(boolean visible) {
+        int userId = UserHandle.USER_SYSTEM;
+        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+        if (visible) {
+            onUser(USER_VISIBLE, userId);
+        } else {
+            onUser(USER_INVISIBLE, userId);
+        }
+    }
+
+    /**
      * Unlocks the given user.
      */
     public void onUserUnlocking(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 3f1d1fe..ae50b23 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -186,6 +186,10 @@
         synchronized (mVpns) {
             for (int i = 0; i < mVpns.size(); i++) {
                 pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
+                pw.increaseIndent();
+                mVpns.valueAt(i).dump(pw);
+                pw.decreaseIndent();
+                pw.println();
             }
             pw.decreaseIndent();
         }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 672ee0e..1a163a8 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -528,7 +528,7 @@
     private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
             List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
         if (!packageExistsForUser(packageName, accounts.userId)) {
-            Log.d(TAG, "Package not found " + packageName);
+            Log.w(TAG, "getAccountsAndVisibilityForPackage#Package not found " + packageName);
             return new LinkedHashMap<>();
         }
 
@@ -677,7 +677,7 @@
                 restoreCallingIdentity(identityToken);
             }
         } catch (NameNotFoundException e) {
-            Log.d(TAG, "Package not found " + e.getMessage());
+            Log.w(TAG, "resolveAccountVisibility#Package not found " + e.getMessage());
             return AccountManager.VISIBILITY_NOT_VISIBLE;
         }
 
@@ -756,7 +756,7 @@
             }
             return true;
         } catch (NameNotFoundException e) {
-            Log.d(TAG, "Package not found " + e.getMessage());
+            Log.w(TAG, "isPreOApplication#Package not found " + e.getMessage());
             return true;
         }
     }
@@ -4063,7 +4063,7 @@
             int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
             return hasAccountAccess(account, packageName, uid);
         } catch (NameNotFoundException e) {
-            Log.d(TAG, "Package not found " + e.getMessage());
+            Log.w(TAG, "hasAccountAccess#Package not found " + e.getMessage());
             return false;
         }
     }
@@ -4195,7 +4195,7 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            AccountAndUser[] allAccounts = getAllAccounts();
+            AccountAndUser[] allAccounts = getAllAccountsForSystemProcess();
             for (int i = allAccounts.length - 1; i >= 0; i--) {
                 if (allAccounts[i].account.equals(account)) {
                     return true;
@@ -4345,10 +4345,11 @@
     /**
      * Returns accounts for all running users, ignores visibility values.
      *
+     * Should only be called by System process.
      * @hide
      */
     @NonNull
-    public AccountAndUser[] getRunningAccounts() {
+    public AccountAndUser[] getRunningAccountsForSystem() {
         final int[] runningUserIds;
         try {
             runningUserIds = ActivityManager.getService().getRunningUserIds();
@@ -4356,26 +4357,34 @@
             // Running in system_server; should never happen
             throw new RuntimeException(e);
         }
-        return getAccounts(runningUserIds);
+        return getAccountsForSystem(runningUserIds);
     }
 
     /**
      * Returns accounts for all users, ignores visibility values.
      *
+     * Should only be called by system process
+     *
      * @hide
      */
     @NonNull
-    public AccountAndUser[] getAllAccounts() {
+    public AccountAndUser[] getAllAccountsForSystemProcess() {
         final List<UserInfo> users = getUserManager().getAliveUsers();
         final int[] userIds = new int[users.size()];
         for (int i = 0; i < userIds.length; i++) {
             userIds[i] = users.get(i).id;
         }
-        return getAccounts(userIds);
+        return getAccountsForSystem(userIds);
     }
 
+    /**
+     * Returns all accounts for the given user, ignores all visibility checks.
+     * This should only be called by system process.
+     *
+     * @hide
+     */
     @NonNull
-    private AccountAndUser[] getAccounts(int[] userIds) {
+    private AccountAndUser[] getAccountsForSystem(int[] userIds) {
         final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
         for (int userId : userIds) {
             UserAccounts userAccounts = getUserAccounts(userId);
@@ -4384,7 +4393,7 @@
                     userAccounts,
                     null /* type */,
                     Binder.getCallingUid(),
-                    null /* packageName */,
+                    "android"/* packageName */,
                     false /* include managed not visible*/);
             for (Account account : accounts) {
                 runningAccounts.add(new AccountAndUser(account, userId));
@@ -4795,6 +4804,7 @@
 
     private abstract class Session extends IAccountAuthenticatorResponse.Stub
             implements IBinder.DeathRecipient, ServiceConnection {
+        private final Object mSessionLock = new Object();
         IAccountManagerResponse mResponse;
         final String mAccountType;
         final boolean mExpectActivityLaunch;
@@ -4985,9 +4995,11 @@
         }
 
         private void unbind() {
-            if (mAuthenticator != null) {
-                mAuthenticator = null;
-                mContext.unbindService(this);
+            synchronized (mSessionLock) {
+                if (mAuthenticator != null) {
+                    mAuthenticator = null;
+                    mContext.unbindService(this);
+                }
             }
         }
 
@@ -4997,12 +5009,14 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
-            try {
-                run();
-            } catch (RemoteException e) {
-                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
-                        "remote exception");
+            synchronized (mSessionLock) {
+                mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
+                try {
+                    run();
+                } catch (RemoteException e) {
+                    onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                            "remote exception");
+                }
             }
         }
 
@@ -5355,7 +5369,7 @@
             }
         } else {
             Account[] accounts = getAccountsFromCache(userAccounts, null /* type */,
-                    Process.SYSTEM_UID, null /* packageName */, false);
+                    Process.SYSTEM_UID, "android" /* packageName */, false);
             fout.println("Accounts: " + accounts.length);
             for (Account account : accounts) {
                 fout.println("  " + account.toString());
@@ -5550,7 +5564,7 @@
                         return true;
                     }
                 } catch (PackageManager.NameNotFoundException e) {
-                    Log.d(TAG, "Package not found " + e.getMessage());
+                    Log.w(TAG, "isPrivileged#Package not found " + e.getMessage());
                 }
             }
         } finally {
@@ -6074,7 +6088,7 @@
                     }
                 }
             } catch (NameNotFoundException e) {
-                Log.d(TAG, "Package not found " + e.getMessage());
+                Log.w(TAG, "filterSharedAccounts#Package not found " + e.getMessage());
             }
             Map<Account, Integer> filtered = new LinkedHashMap<>();
             for (Map.Entry<Account, Integer> entry : unfiltered.entrySet()) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c677edc..d0f245f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1197,7 +1197,7 @@
         }
 
         FrameworkStatsLog.write(SERVICE_REQUEST_EVENT_REPORTED, uid, callingUid,
-                ActivityManagerService.getShortAction(service.getAction()),
+                service.getAction(),
                 SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__START, false,
                 r.app == null || r.app.getThread() == null
                 ? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ab73e48..2761a86 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11111,11 +11111,13 @@
         final long pss;
         final long swapPss;
         final long mRss;
-        final int id;
+        final int id; // pid
+        final int userId;
         final boolean hasActivities;
         ArrayList<MemItem> subitems;
 
         MemItem(String label, String shortLabel, long pss, long swapPss, long rss, int id,
+                @UserIdInt int userId,
                 boolean hasActivities) {
             this.isProc = true;
             this.label = label;
@@ -11124,6 +11126,7 @@
             this.swapPss = swapPss;
             this.mRss = rss;
             this.id = id;
+            this.userId = userId;
             this.hasActivities = hasActivities;
         }
 
@@ -11135,6 +11138,7 @@
             this.swapPss = swapPss;
             this.mRss = rss;
             this.id = id;
+            this.userId = UserHandle.USER_SYSTEM;
             this.hasActivities = false;
         }
     }
@@ -11169,8 +11173,9 @@
                     pw.printf("%s%s: %-60s (%s in swap)\n", prefix, stringifyKBSize(mi.pss),
                             mi.label, stringifyKBSize(mi.swapPss));
                 } else {
-                    pw.printf("%s%s: %s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
-                            mi.label);
+                    pw.printf("%s%s: %s %s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
+                            mi.label,
+                            mi.userId != UserHandle.USER_SYSTEM ? "(user " + mi.userId + ")" : "");
                 }
             } else if (mi.isProc) {
                 pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
@@ -11662,7 +11667,7 @@
                     ss[INDEX_TOTAL_MEMTRACK_GL] += memtrackGl;
                     MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
                             (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
-                            myTotalSwapPss, myTotalRss, pid, hasActivities);
+                            myTotalSwapPss, myTotalRss, pid, r.userId, hasActivities);
                     procMems.add(pssItem);
                     procMemsMap.put(pid, pssItem);
 
@@ -11759,7 +11764,7 @@
 
                     MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                             st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
-                            st.pid, false);
+                            st.pid, UserHandle.getUserId(st.uid), false);
                     procMems.add(pssItem);
 
                     ss[INDEX_NATIVE_PSS] += info.nativePss;
@@ -12305,7 +12310,7 @@
                 ss[INDEX_TOTAL_RSS] += myTotalRss;
                 MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
                         (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss,
-                        myTotalSwapPss, myTotalRss, pid, hasActivities);
+                        myTotalSwapPss, myTotalRss, pid, r.userId, hasActivities);
                 procMems.add(pssItem);
                 procMemsMap.put(pid, pssItem);
 
@@ -12393,7 +12398,7 @@
 
                     MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                             st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss,
-                            st.pid, false);
+                            st.pid, UserHandle.getUserId(st.uid), false);
                     procMems.add(pssItem);
 
                     ss[INDEX_NATIVE_PSS] += info.nativePss;
@@ -14723,6 +14728,15 @@
         mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
     }
 
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+    }
+
     final Intent verifyBroadcastLocked(Intent intent) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors() == true) {
@@ -17417,6 +17431,21 @@
         }
 
         @Override
+        public int broadcastIntentWithCallback(Intent intent,
+                IIntentReceiver resultTo,
+                String[] requiredPermissions,
+                int userId, int[] appIdAllowList,
+                @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+                @Nullable Bundle bOptions) {
+            // Sending broadcasts with a finish callback without the need for the broadcasts
+            // delivery to be serialized is only supported by modern queue. So, when modern
+            // queue is disabled, we continue to send broadcasts in a serialized fashion.
+            final boolean serialized = !isModernQueueEnabled();
+            return broadcastIntent(intent, resultTo, requiredPermissions, serialized, userId,
+                    appIdAllowList, filterExtrasForReceiver, bOptions);
+        }
+
+        @Override
         public ComponentName startServiceInPackage(int uid, Intent service, String resolvedType,
                 boolean fgRequired, String callingPackage, @Nullable String callingFeatureId,
                 int userId, boolean allowBackgroundActivityStarts,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 75d1f68..d1bcf87 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -731,6 +731,8 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+        super.getBatteryUsageStats_enforcePermission();
+
         awaitCompletion();
 
         if (mBatteryUsageStatsProvider.shouldUpdateStats(queries,
@@ -846,6 +848,8 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long computeBatteryScreenOffRealtimeMs() {
+        super.computeBatteryScreenOffRealtimeMs_enforcePermission();
+
         synchronized (mStats) {
             final long curTimeUs = SystemClock.elapsedRealtimeNanos() / 1000;
             long timeUs = mStats.computeBatteryScreenOffRealtime(curTimeUs,
@@ -857,6 +861,8 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long getScreenOffDischargeMah() {
+        super.getScreenOffDischargeMah_enforcePermission();
+
         synchronized (mStats) {
             long dischargeUah = mStats.getUahDischargeScreenOff(BatteryStats.STATS_SINCE_CHARGED);
             return dischargeUah / 1000;
@@ -866,6 +872,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteEvent(final int code, final String name, final int uid) {
+        super.noteEvent_enforcePermission();
+
         if (name == null) {
             // TODO(b/194733136): Replace with an IllegalArgumentException throw.
             Slog.wtfStack(TAG, "noteEvent called with null name. code = " + code);
@@ -886,6 +894,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteSyncStart(final String name, final int uid) {
+        super.noteSyncStart_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -902,6 +912,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteSyncFinish(final String name, final int uid) {
+        super.noteSyncFinish_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -919,6 +931,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteJobStart(final String name, final int uid) {
+        super.noteJobStart_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -934,6 +948,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteJobFinish(final String name, final int uid, final int stopReason) {
+        super.noteJobFinish_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1007,6 +1023,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartWakelock(final int uid, final int pid, final String name,
             final String historyName, final int type, final boolean unimportantForLogging) {
+        super.noteStartWakelock_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1023,6 +1041,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopWakelock(final int uid, final int pid, final String name,
             final String historyName, final int type) {
+        super.noteStopWakelock_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1039,6 +1059,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartWakelockFromSource(final WorkSource ws, final int pid, final String name,
             final String historyName, final int type, final boolean unimportantForLogging) {
+        super.noteStartWakelockFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1058,6 +1080,8 @@
             final String historyName, final int type, final WorkSource newWs, final int newPid,
             final String newName, final String newHistoryName, final int newType,
             final boolean newUnimportantForLogging) {
+        super.noteChangeWakelockFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
         synchronized (mLock) {
@@ -1077,6 +1101,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopWakelockFromSource(final WorkSource ws, final int pid, final String name,
             final String historyName, final int type) {
+        super.noteStopWakelockFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1094,6 +1120,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockStart(final String name, final String historyName,
             final int uid) {
+        super.noteLongPartialWakelockStart_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1110,6 +1138,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockStartFromSource(final String name, final String historyName,
             final WorkSource workSource) {
+        super.noteLongPartialWakelockStartFromSource_enforcePermission();
+
         final WorkSource localWs = workSource != null ? new WorkSource(workSource) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1127,6 +1157,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockFinish(final String name, final String historyName,
             final int uid) {
+        super.noteLongPartialWakelockFinish_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1143,6 +1175,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteLongPartialWakelockFinishFromSource(final String name, final String historyName,
             final WorkSource workSource) {
+        super.noteLongPartialWakelockFinishFromSource_enforcePermission();
+
         final WorkSource localWs = workSource != null ? new WorkSource(workSource) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1159,6 +1193,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartSensor(final int uid, final int sensor) {
+        super.noteStartSensor_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1175,6 +1211,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopSensor(final int uid, final int sensor) {
+        super.noteStopSensor_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1191,6 +1229,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteVibratorOn(final int uid, final long durationMillis) {
+        super.noteVibratorOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1205,6 +1245,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteVibratorOff(final int uid) {
+        super.noteVibratorOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1219,6 +1261,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteGpsChanged(final WorkSource oldWs, final WorkSource newWs) {
+        super.noteGpsChanged_enforcePermission();
+
         final WorkSource localOldWs = oldWs != null ? new WorkSource(oldWs) : null;
         final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
         synchronized (mLock) {
@@ -1235,6 +1279,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteGpsSignalQuality(final int signalLevel) {
+        super.noteGpsSignalQuality_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1249,6 +1295,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteScreenState(final int state) {
+        super.noteScreenState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1267,6 +1315,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteScreenBrightness(final int brightness) {
+        super.noteScreenBrightness_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1282,6 +1332,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteUserActivity(final int uid, final int event) {
+        super.noteUserActivity_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1296,6 +1348,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWakeUp(final String reason, final int reasonUid) {
+        super.noteWakeUp_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1310,6 +1364,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteInteractive(final boolean interactive) {
+        super.noteInteractive_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             mHandler.post(() -> {
@@ -1323,6 +1379,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteConnectivityChanged(final int type, final String extra) {
+        super.noteConnectivityChanged_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1338,6 +1396,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteMobileRadioPowerState(final int powerState, final long timestampNs,
             final int uid) {
+        super.noteMobileRadioPowerState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1364,6 +1424,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneOn() {
+        super.notePhoneOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1378,6 +1440,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneOff() {
+        super.notePhoneOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1392,6 +1456,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneSignalStrength(final SignalStrength signalStrength) {
+        super.notePhoneSignalStrength_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1407,6 +1473,8 @@
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
             final int serviceType, final int nrFrequency) {
+        super.notePhoneDataConnectionState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1422,6 +1490,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void notePhoneState(final int state) {
+        super.notePhoneState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1437,6 +1507,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiOn() {
+        super.noteWifiOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1453,6 +1525,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiOff() {
+        super.noteWifiOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1469,6 +1543,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartAudio(final int uid) {
+        super.noteStartAudio_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1485,6 +1561,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopAudio(final int uid) {
+        super.noteStopAudio_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1501,6 +1579,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartVideo(final int uid) {
+        super.noteStartVideo_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1517,6 +1597,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopVideo(final int uid) {
+        super.noteStopVideo_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1533,6 +1615,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetAudio() {
+        super.noteResetAudio_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1549,6 +1633,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetVideo() {
+        super.noteResetVideo_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1565,6 +1651,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFlashlightOn(final int uid) {
+        super.noteFlashlightOn_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1581,6 +1669,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFlashlightOff(final int uid) {
+        super.noteFlashlightOff_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1597,6 +1687,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStartCamera(final int uid) {
+        super.noteStartCamera_enforcePermission();
+
         if (DBG) Slog.d(TAG, "begin noteStartCamera");
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1615,6 +1707,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteStopCamera(final int uid) {
+        super.noteStopCamera_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1631,6 +1725,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetCamera() {
+        super.noteResetCamera_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1647,6 +1743,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteResetFlashlight() {
+        super.noteResetFlashlight_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1663,6 +1761,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRadioPowerState(final int powerState, final long tsNanos, final int uid) {
+        super.noteWifiRadioPowerState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1694,6 +1794,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRunning(final WorkSource ws) {
+        super.noteWifiRunning_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1712,6 +1814,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRunningChanged(final WorkSource oldWs, final WorkSource newWs) {
+        super.noteWifiRunningChanged_enforcePermission();
+
         final WorkSource localOldWs = oldWs != null ? new WorkSource(oldWs) : null;
         final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
         synchronized (mLock) {
@@ -1733,6 +1837,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiStopped(final WorkSource ws) {
+        super.noteWifiStopped_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : ws;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1750,6 +1856,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiState(final int wifiState, final String accessPoint) {
+        super.noteWifiState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             mHandler.post(() -> {
@@ -1763,6 +1871,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiSupplicantStateChanged(final int supplState, final boolean failedAuth) {
+        super.noteWifiSupplicantStateChanged_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1778,6 +1888,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiRssiChanged(final int newRssi) {
+        super.noteWifiRssiChanged_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1792,6 +1904,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockAcquired(final int uid) {
+        super.noteFullWifiLockAcquired_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1806,6 +1920,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockReleased(final int uid) {
+        super.noteFullWifiLockReleased_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1820,6 +1936,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStarted(final int uid) {
+        super.noteWifiScanStarted_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1834,6 +1952,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStopped(final int uid) {
+        super.noteWifiScanStopped_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1848,6 +1968,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiMulticastEnabled(final int uid) {
+        super.noteWifiMulticastEnabled_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1862,6 +1984,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiMulticastDisabled(final int uid) {
+        super.noteWifiMulticastDisabled_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -1876,6 +2000,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockAcquiredFromSource(final WorkSource ws) {
+        super.noteFullWifiLockAcquiredFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1892,6 +2018,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteFullWifiLockReleasedFromSource(final WorkSource ws) {
+        super.noteFullWifiLockReleasedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1908,6 +2036,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStartedFromSource(final WorkSource ws) {
+        super.noteWifiScanStartedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1923,6 +2053,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiScanStoppedFromSource(final WorkSource ws) {
+        super.noteWifiScanStoppedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1938,6 +2070,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiBatchedScanStartedFromSource(final WorkSource ws, final int csph) {
+        super.noteWifiBatchedScanStartedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1954,6 +2088,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiBatchedScanStoppedFromSource(final WorkSource ws) {
+        super.noteWifiBatchedScanStoppedFromSource_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1970,6 +2106,8 @@
     @Override
     @EnforcePermission(anyOf = {NETWORK_STACK, PERMISSION_MAINLINE_NETWORK_STACK})
     public void noteNetworkInterfaceForTransports(final String iface, int[] transportTypes) {
+        super.noteNetworkInterfaceForTransports_enforcePermission();
+
         synchronized (mLock) {
             mHandler.post(() -> {
                 mStats.noteNetworkInterfaceForTransports(iface, transportTypes);
@@ -1983,6 +2121,8 @@
         // During device boot, qtaguid isn't enabled until after the inital
         // loading of battery stats. Now that they're enabled, take our initial
         // snapshot for future delta calculation.
+        super.noteNetworkStatsEnabled_enforcePermission();
+
         synchronized (mLock) {
             // Still schedule it on the handler to make sure we have existing pending works done
             mHandler.post(() -> {
@@ -1996,6 +2136,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteDeviceIdleMode(final int mode, final String activeReason, final int activeUid) {
+        super.noteDeviceIdleMode_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -2039,6 +2181,8 @@
     @Override
     @EnforcePermission(BLUETOOTH_CONNECT)
     public void noteBluetoothOn(int uid, int reason, String packageName) {
+        super.noteBluetoothOn_enforcePermission();
+
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
                 Binder.getCallingUid(), null,
                 FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
@@ -2051,6 +2195,8 @@
     @Override
     @EnforcePermission(BLUETOOTH_CONNECT)
     public void noteBluetoothOff(int uid, int reason, String packageName) {
+        super.noteBluetoothOff_enforcePermission();
+
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
                 Binder.getCallingUid(), null,
                 FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
@@ -2060,6 +2206,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
+        super.noteBleScanStarted_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2076,6 +2224,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanStopped(final WorkSource ws, final boolean isUnoptimized) {
+        super.noteBleScanStopped_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2092,6 +2242,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanReset() {
+        super.noteBleScanReset_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -2106,6 +2258,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBleScanResults(final WorkSource ws, final int numNewResults) {
+        super.noteBleScanResults_enforcePermission();
+
         final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2122,6 +2276,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteWifiControllerActivity(final WifiActivityEnergyInfo info) {
+        super.noteWifiControllerActivity_enforcePermission();
+
         if (info == null || !info.isValid()) {
             Slog.e(TAG, "invalid wifi data given: " + info);
             return;
@@ -2142,6 +2298,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteBluetoothControllerActivity(final BluetoothActivityEnergyInfo info) {
+        super.noteBluetoothControllerActivity_enforcePermission();
+
         if (info == null || !info.isValid()) {
             Slog.e(TAG, "invalid bluetooth data given: " + info);
             return;
@@ -2162,6 +2320,8 @@
     @Override
     @EnforcePermission(UPDATE_DEVICE_STATS)
     public void noteModemControllerActivity(final ModemActivityInfo info) {
+        super.noteModemControllerActivity_enforcePermission();
+
         if (info == null) {
             Slog.e(TAG, "invalid modem data given: " + info);
             return;
@@ -2188,6 +2348,8 @@
     public void setBatteryState(final int status, final int health, final int plugType,
             final int level, final int temp, final int volt, final int chargeUAh,
             final int chargeFullUAh, final long chargeTimeToFullSeconds) {
+        super.setBatteryState_enforcePermission();
+
         synchronized (mLock) {
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
@@ -2230,12 +2392,16 @@
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long getAwakeTimeBattery() {
+        super.getAwakeTimeBattery_enforcePermission();
+
         return mStats.getAwakeTimeBattery();
     }
 
     @Override
     @EnforcePermission(BATTERY_STATS)
     public long getAwakeTimePlugged() {
+        super.getAwakeTimePlugged_enforcePermission();
+
         return mStats.getAwakeTimePlugged();
     }
 
@@ -2738,6 +2904,8 @@
     @EnforcePermission(anyOf = {UPDATE_DEVICE_STATS, BATTERY_STATS})
     public CellularBatteryStats getCellularBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getCellularBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getCellularBatteryStats();
@@ -2752,6 +2920,8 @@
     @EnforcePermission(anyOf = {UPDATE_DEVICE_STATS, BATTERY_STATS})
     public WifiBatteryStats getWifiBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getWifiBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getWifiBatteryStats();
@@ -2766,6 +2936,8 @@
     @EnforcePermission(BATTERY_STATS)
     public GpsBatteryStats getGpsBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getGpsBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getGpsBatteryStats();
@@ -2780,6 +2952,8 @@
     @EnforcePermission(BATTERY_STATS)
     public WakeLockStats getWakeLockStats() {
         // Wait for the completion of pending works if there is any
+        super.getWakeLockStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getWakeLockStats();
@@ -2794,6 +2968,8 @@
     @EnforcePermission(BATTERY_STATS)
     public BluetoothBatteryStats getBluetoothBatteryStats() {
         // Wait for the completion of pending works if there is any
+        super.getBluetoothBatteryStats_enforcePermission();
+
         awaitCompletion();
         synchronized (mStats) {
             return mStats.getBluetoothBatteryStats();
@@ -2901,6 +3077,8 @@
      */
     @EnforcePermission(POWER_SAVER)
     public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+        super.setChargingStateUpdateDelayMillis_enforcePermission();
+
         final long ident = Binder.clearCallingIdentity();
 
         try {
@@ -3051,6 +3229,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void setChargerAcOnline(boolean online, boolean forceUpdate) {
+        super.setChargerAcOnline_enforcePermission();
+
         mBatteryManagerInternal.setChargerAcOnline(online, forceUpdate);
     }
 
@@ -3060,6 +3240,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void setBatteryLevel(int level, boolean forceUpdate) {
+        super.setBatteryLevel_enforcePermission();
+
         mBatteryManagerInternal.setBatteryLevel(level, forceUpdate);
     }
 
@@ -3069,6 +3251,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void unplugBattery(boolean forceUpdate) {
+        super.unplugBattery_enforcePermission();
+
         mBatteryManagerInternal.unplugBattery(forceUpdate);
     }
 
@@ -3078,6 +3262,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void resetBattery(boolean forceUpdate) {
+        super.resetBattery_enforcePermission();
+
         mBatteryManagerInternal.resetBattery(forceUpdate);
     }
 
@@ -3087,6 +3273,8 @@
     @Override
     @EnforcePermission(DEVICE_POWER)
     public void suspendBatteryInput() {
+        super.suspendBatteryInput_enforcePermission();
+
         mBatteryManagerInternal.suspendBatteryInput();
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 417a0e5..9d96008 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -175,7 +175,7 @@
      */
     public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
     private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
-    private static final long DEFAULT_DELAY_CACHED_MILLIS = 0;
+    private static final long DEFAULT_DELAY_CACHED_MILLIS = +30_000;
 
     /**
      * For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 2e12309..47ca427 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -135,14 +135,6 @@
     private int mActiveIndex;
 
     /**
-     * When defined, the receiver actively being dispatched into this process
-     * was considered "blocked" until at least the given count of other
-     * receivers have reached a terminal state; typically used for ordered
-     * broadcasts and priority traunches.
-     */
-    private int mActiveBlockedUntilTerminalCount;
-
-    /**
      * Count of {@link #mActive} broadcasts that have been dispatched since this
      * queue was last idle.
      */
@@ -206,15 +198,11 @@
      * given count of other receivers have reached a terminal state; typically
      * used for ordered broadcasts and priority traunches.
      */
-    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
-            int blockedUntilTerminalCount) {
+    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
         if (record.isReplacePending()) {
-            boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex,
-                    blockedUntilTerminalCount)
-                    || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex,
-                            blockedUntilTerminalCount)
-                    || replaceBroadcastInQueue(mPendingOffload, record, recordIndex,
-                            blockedUntilTerminalCount);
+            boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex)
+                    || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex)
+                    || replaceBroadcastInQueue(mPendingOffload, record, recordIndex);
             if (didReplace) {
                 return;
             }
@@ -225,7 +213,6 @@
         SomeArgs newBroadcastArgs = SomeArgs.obtain();
         newBroadcastArgs.arg1 = record;
         newBroadcastArgs.argi1 = recordIndex;
-        newBroadcastArgs.argi2 = blockedUntilTerminalCount;
 
         // Cross-broadcast prioritization policy:  some broadcasts might warrant being
         // issued ahead of others that are already pending, for example if this new
@@ -244,7 +231,7 @@
      * {@code false} otherwise.
      */
     private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
-            @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+            @NonNull BroadcastRecord record, int recordIndex) {
         final Iterator<SomeArgs> it = queue.descendingIterator();
         final Object receiver = record.receivers.get(recordIndex);
         while (it.hasNext()) {
@@ -259,7 +246,6 @@
                 // Exact match found; perform in-place swap
                 args.arg1 = record;
                 args.argi1 = recordIndex;
-                args.argi2 = blockedUntilTerminalCount;
                 onBroadcastDequeued(testRecord, testRecordIndex);
                 onBroadcastEnqueued(record, recordIndex);
                 return true;
@@ -411,7 +397,6 @@
         final SomeArgs next = removeNextBroadcast();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
-        mActiveBlockedUntilTerminalCount = next.argi2;
         mActiveCountSinceIdle++;
         mActiveViaColdStart = false;
         next.recycle();
@@ -424,7 +409,6 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
-        mActiveBlockedUntilTerminalCount = -1;
         mActiveCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
@@ -659,6 +643,7 @@
     static final int REASON_CONTAINS_INTERACTIVE = 14;
     static final int REASON_CONTAINS_RESULT_TO = 15;
     static final int REASON_CONTAINS_INSTRUMENTED = 16;
+    static final int REASON_CONTAINS_MANIFEST = 17;
 
     @IntDef(flag = false, prefix = { "REASON_" }, value = {
             REASON_EMPTY,
@@ -674,6 +659,7 @@
             REASON_CONTAINS_INTERACTIVE,
             REASON_CONTAINS_RESULT_TO,
             REASON_CONTAINS_INSTRUMENTED,
+            REASON_CONTAINS_MANIFEST,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reason {}
@@ -693,6 +679,7 @@
             case REASON_CONTAINS_INTERACTIVE: return "CONTAINS_INTERACTIVE";
             case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
             case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
+            case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
             default: return Integer.toString(reason);
         }
     }
@@ -705,7 +692,7 @@
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
-            final int blockedUntilTerminalCount = next.argi2;
+            final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
             final long runnableAt = r.enqueueTime;
 
             // We might be blocked waiting for other receivers to finish,
@@ -741,6 +728,9 @@
             } else if (mCountResultTo > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+            } else if (mCountManifest > 0) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CONTAINS_MANIFEST;
             } else if (mProcessCached) {
                 mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
                 mRunnableAtReason = REASON_CACHED;
@@ -871,19 +861,19 @@
         pw.println();
         pw.increaseIndent();
         if (mActive != null) {
-            dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
+            dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
         }
         for (SomeArgs args : mPendingUrgent) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord("URGENT", now, pw, r, args.argi1, args.argi2);
+            dumpRecord("URGENT", now, pw, r, args.argi1);
         }
         for (SomeArgs args : mPending) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord(null, now, pw, r, args.argi1, args.argi2);
+            dumpRecord(null, now, pw, r, args.argi1);
         }
         for (SomeArgs args : mPendingOffload) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord("OFFLOAD", now, pw, r, args.argi1, args.argi2);
+            dumpRecord("OFFLOAD", now, pw, r, args.argi1);
         }
         pw.decreaseIndent();
         pw.println();
@@ -891,8 +881,7 @@
 
     @NeverCompile
     private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
-            @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex,
-            int blockedUntilTerminalCount) {
+            @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
         TimeUtils.formatDuration(record.enqueueTime, now, pw);
         pw.print(' ');
         pw.println(record.toShortString());
@@ -918,6 +907,7 @@
             pw.print(info.activityInfo.name);
         }
         pw.println();
+        final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
         if (blockedUntilTerminalCount != -1) {
             pw.print("    blocked until ");
             pw.print(blockedUntilTerminalCount);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index ffc54d9..454b284 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1690,13 +1690,7 @@
                 System.identityHashCode(original));
         }
 
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-
+        mService.notifyBroadcastFinishedLocked(original);
         mHistory.addBroadcastToHistoryLocked(original);
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index df1f0fd..c3839a9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -31,7 +31,6 @@
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
 import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
-import static com.android.server.am.BroadcastRecord.getReceiverPriority;
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
@@ -320,7 +319,6 @@
             return;
         }
 
-        final int cookie = traceBegin("updateRunnableList");
         final boolean wantQueue = queue.isRunnable();
         final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
                 || (queue.runnableAtNext != null);
@@ -347,8 +345,6 @@
         if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
             removeProcessQueue(queue.processName, queue.uid);
         }
-
-        traceEnd(cookie);
     }
 
     /**
@@ -591,36 +587,11 @@
         r.enqueueRealTime = SystemClock.elapsedRealtime();
         r.enqueueClockTime = System.currentTimeMillis();
 
-        int lastPriority = 0;
-        int lastPriorityIndex = 0;
-
         for (int i = 0; i < r.receivers.size(); i++) {
             final Object receiver = r.receivers.get(i);
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     getReceiverProcessName(receiver), getReceiverUid(receiver));
-
-            final int blockedUntilTerminalCount;
-            if (r.ordered) {
-                // When sending an ordered broadcast, we need to block this
-                // receiver until all previous receivers have terminated
-                blockedUntilTerminalCount = i;
-            } else if (r.prioritized) {
-                // When sending a prioritized broadcast, we only need to wait
-                // for the previous traunch of receivers to be terminated
-                final int thisPriority = getReceiverPriority(receiver);
-                if ((i == 0) || (thisPriority != lastPriority)) {
-                    lastPriority = thisPriority;
-                    lastPriorityIndex = i;
-                    blockedUntilTerminalCount = i;
-                } else {
-                    blockedUntilTerminalCount = lastPriorityIndex;
-                }
-            } else {
-                // Otherwise we don't need to block at all
-                blockedUntilTerminalCount = -1;
-            }
-
-            queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
+            queue.enqueueOrReplaceBroadcast(r, i);
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
@@ -1431,6 +1402,7 @@
 
         final boolean recordFinished = (r.terminalCount == r.receivers.size());
         if (recordFinished) {
+            mService.notifyBroadcastFinishedLocked(r);
             mHistory.addBroadcastToHistoryLocked(r);
 
             r.finishTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 65f9b9b..6ea2dee 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -96,6 +96,7 @@
     final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
     final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState int[] delivery;   // delivery state of each receiver
+    final int[] blockedUntilTerminalCount; // blocked until count of each receiver
     @Nullable ProcessRecord resultToApp; // who receives final result if non-null
     @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -375,6 +376,7 @@
         options = _options;
         receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
+        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
         resultToApp = _resultToApp;
@@ -385,7 +387,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(receivers);
+        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -427,6 +429,7 @@
         options = from.options;
         receivers = from.receivers;
         delivery = from.delivery;
+        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -690,22 +693,60 @@
     }
 
     /**
-     * Return if given receivers list has more than one traunch of priorities.
+     * Determine if the result of {@link #calculateBlockedUntilTerminalCount}
+     * has prioritized tranches of receivers.
      */
     @VisibleForTesting
-    static boolean isPrioritized(@NonNull List<Object> receivers) {
-        int firstPriority = 0;
-        for (int i = 0; i < receivers.size(); i++) {
-            final int thisPriority = getReceiverPriority(receivers.get(i));
-            if (i == 0) {
-                firstPriority = thisPriority;
-            } else if (thisPriority != firstPriority) {
-                return true;
-            }
-        }
-        return false;
+    static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+            boolean ordered) {
+        return !ordered && (blockedUntilTerminalCount.length > 0)
+                && (blockedUntilTerminalCount[0] != -1);
     }
 
+    /**
+     * Calculate the {@link #terminalCount} that each receiver should be
+     * considered blocked until.
+     * <p>
+     * For example, in an ordered broadcast, receiver {@code N} is blocked until
+     * receiver {@code N-1} reaches a terminal state. Similarly, in a
+     * prioritized broadcast, receiver {@code N} is blocked until all receivers
+     * of a higher priority reach a terminal state.
+     * <p>
+     * When there are no terminal count constraints, the blocked value for each
+     * receiver is {@code -1}.
+     */
+    @VisibleForTesting
+    static @NonNull int[] calculateBlockedUntilTerminalCount(
+            @NonNull List<Object> receivers, boolean ordered) {
+        final int N = receivers.size();
+        final int[] blockedUntilTerminalCount = new int[N];
+        int lastPriority = 0;
+        int lastPriorityIndex = 0;
+        for (int i = 0; i < N; i++) {
+            if (ordered) {
+                // When sending an ordered broadcast, we need to block this
+                // receiver until all previous receivers have terminated
+                blockedUntilTerminalCount[i] = i;
+            } else {
+                // When sending a prioritized broadcast, we only need to wait
+                // for the previous tranche of receivers to be terminated
+                final int thisPriority = getReceiverPriority(receivers.get(i));
+                if ((i == 0) || (thisPriority != lastPriority)) {
+                    lastPriority = thisPriority;
+                    lastPriorityIndex = i;
+                    blockedUntilTerminalCount[i] = i;
+                } else {
+                    blockedUntilTerminalCount[i] = lastPriorityIndex;
+                }
+            }
+        }
+        // If the entire list is in the same priority tranche, mark as -1 to
+        // indicate that none of them need to wait
+        if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
+            Arrays.fill(blockedUntilTerminalCount, -1);
+        }
+        return blockedUntilTerminalCount;
+    }
 
     static int getReceiverUid(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index dec8b62..60e6754 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -116,7 +116,7 @@
 30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
 30087 ssm_user_stopped (userId|1|5)
 30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visible (userId|1|5)
+30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
 
 # Foreground service start/stop events.
 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 3f5f3bc..fed0b11 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -396,11 +396,16 @@
                 resolvedType = key.requestResolvedType;
             }
 
-            // Apply any launch flags from the ActivityOptions. This is to ensure that the caller
-            // can specify a consistent launch mode even if the PendingIntent is immutable
+            // Apply any launch flags from the ActivityOptions. This is used only by SystemUI
+            // to ensure that we can launch the pending intent with a consistent launch mode even
+            // if the provided PendingIntent is immutable (ie. to force an activity to launch into
+            // a new task, or to launch multiple instances if supported by the app)
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
-                finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
+                // TODO(b/254490217): Move this check into SafeActivityOptions
+                if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
+                    finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
+                }
             }
 
             // Extract options before clearing calling identity
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 33e4070..438a2d43 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -567,6 +567,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) {
+        super.getCurrentStats_enforcePermission();
+
         Parcel current = Parcel.obtain();
         synchronized (mLock) {
             long now = SystemClock.uptimeMillis();
@@ -623,6 +625,8 @@
     public long getCommittedStatsMerged(long highWaterMarkMs, int section, boolean doAggregate,
             List<ParcelFileDescriptor> committedStats, ProcessStats mergedStats) {
 
+        super.getCommittedStatsMerged_enforcePermission();
+
         long newHighWaterMark = highWaterMarkMs;
         mFileLock.lock();
         try {
@@ -709,6 +713,8 @@
     @android.annotation.EnforcePermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
     @Override
     public ParcelFileDescriptor getStatsOverTime(long minTime) {
+        super.getStatsOverTime_enforcePermission();
+
         Parcel current = Parcel.obtain();
         long curTime;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8d3890c..af55980 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2190,7 +2190,7 @@
         if (oldUserId == UserHandle.USER_SYSTEM) {
             // System user is never stopped, but its visibility is changed (as it is brought to the
             // background)
-            updateSystemUserVisibility(/* visible= */ false);
+            updateSystemUserVisibility(t, /* visible= */ false);
         }
 
         t.traceEnd(); // end continueUserSwitch
@@ -2549,10 +2549,15 @@
 
     // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
     void onSystemUserStarting() {
-        updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+        if (!UserManager.isHeadlessSystemUserMode()) {
+            // Don't need to call on HSUM because it will be called when the system user is
+            // restarted on background
+            mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+        }
     }
 
-    private void updateSystemUserVisibility(boolean visible) {
+    private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
+        t.traceBegin("update-system-userVisibility-" + visible);
         if (DEBUG_MU) {
             Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
         }
@@ -2564,7 +2569,8 @@
                 mVisibleUsers.delete(userId);
             }
         }
-        mInjector.onUserStarting(userId, visible);
+        mInjector.notifySystemUserVisibilityChanged(visible);
+        t.traceEnd();
     }
 
     /**
@@ -3673,5 +3679,8 @@
             getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
                     visible);
         }
+        void notifySystemUserVisibilityChanged(boolean visible) {
+            getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f8a6462..4fda233 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,6 +41,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -153,6 +154,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.System;
 import android.service.notification.ZenModeConfig;
@@ -174,6 +176,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
@@ -233,6 +236,7 @@
             AudioSystemAdapter.OnVolRangeInitRequestListener {
 
     private static final String TAG = "AS.AudioService";
+    private static final boolean CONFIG_DEFAULT_VAL = false;
 
     private final AudioSystemAdapter mAudioSystem;
     private final SystemServerAdapter mSystemServer;
@@ -987,6 +991,7 @@
      * @param looper Looper to use for the service's message handler. If this is null, an
      *               {@link AudioSystemThread} is created as the messaging thread instead.
      */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public AudioService(Context context, AudioSystemAdapter audioSystem,
             SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
             AppOpsManager appOps) {
@@ -1026,8 +1031,12 @@
         mUseVolumeGroupAliases = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
 
-        mNotifAliasRing = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_alias_ring_notif_stream_types);
+        mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                ActivityThread.currentApplication().getMainExecutor(),
+                this::onDeviceConfigChange);
 
         // Initialize volume
         // Priority 1 - Android Property
@@ -1246,6 +1255,22 @@
     }
 
     /**
+     * Separating notification volume from ring is NOT of aliasing the corresponding streams
+     * @param properties
+     */
+    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changeSet = properties.getKeyset();
+        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+            boolean newNotifAliasRing = !properties.getBoolean(
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+            if (mNotifAliasRing != newNotifAliasRing) {
+                mNotifAliasRing = newNotifAliasRing;
+                updateStreamVolumeAlias(true, TAG);
+            }
+        }
+    }
+
+    /**
      * Called by handling of MSG_INIT_STREAMS_VOLUMES
      */
     private void onInitStreamsAndVolumes() {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 4358ee2..acfc2a7 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -410,6 +410,17 @@
         }
 
         @Override
+        public void resetLockout(int userId, byte[] hardwareAuthToken) throws RemoteException {
+            checkInternalPermission();
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mBiometricService.resetLockout(userId, hardwareAuthToken);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public CharSequence getButtonLabel(
                 int userId,
                 String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index cd30d26..fab7f1d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -72,6 +72,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
+import com.android.server.biometrics.log.BiometricContext;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -100,6 +101,7 @@
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
     private final Random mRandom = new Random();
     @NonNull private final Supplier<Long> mRequestCounter;
+    @NonNull private final BiometricContext mBiometricContext;
 
     @VisibleForTesting
     IStatusBarService mStatusBarService;
@@ -776,6 +778,16 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override // Binder call
+        public void resetLockout(
+                int userId, byte[] hardwareAuthToken) {
+            Slog.d(TAG, "resetLockout(userId=" + userId
+                    + ", hat=" + (hardwareAuthToken == null ? "null " : "present") + ")");
+            mBiometricContext.getAuthSessionCoordinator()
+                    .resetLockoutFor(userId, Authenticators.BIOMETRIC_STRONG, -1);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override // Binder call
         public int getCurrentStrength(int sensorId) {
 
             super.getCurrentStrength_enforcePermission();
@@ -984,6 +996,10 @@
             final AtomicLong generator = new AtomicLong(0);
             return () -> generator.incrementAndGet();
         }
+
+        public BiometricContext getBiometricContext(Context context) {
+            return BiometricContext.getInstance(context);
+        }
     }
 
     /**
@@ -1010,6 +1026,7 @@
         mSettingObserver = mInjector.getSettingObserver(context, mHandler,
                 mEnabledOnKeyguardCallbacks);
         mRequestCounter = mInjector.getRequestGenerator();
+        mBiometricContext = injector.getBiometricContext(context);
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index aec98f0..3813fd1 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -48,8 +48,6 @@
  * the PreAuthInfo should not change any sensor state.
  */
 class PreAuthInfo {
-    private static final String TAG = "BiometricService/PreAuthInfo";
-
     static final int AUTHENTICATOR_OK = 1;
     static final int BIOMETRIC_NO_HARDWARE = 2;
     static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 3;
@@ -62,24 +60,7 @@
     static final int BIOMETRIC_LOCKOUT_TIMED = 10;
     static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
     static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
-    @IntDef({AUTHENTICATOR_OK,
-            BIOMETRIC_NO_HARDWARE,
-            BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
-            BIOMETRIC_INSUFFICIENT_STRENGTH,
-            BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
-            BIOMETRIC_HARDWARE_NOT_DETECTED,
-            BIOMETRIC_NOT_ENROLLED,
-            BIOMETRIC_NOT_ENABLED_FOR_APPS,
-            CREDENTIAL_NOT_ENROLLED,
-            BIOMETRIC_LOCKOUT_TIMED,
-            BIOMETRIC_LOCKOUT_PERMANENT,
-            BIOMETRIC_SENSOR_PRIVACY_ENABLED})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface AuthenticatorStatus {}
-
-    private final boolean mBiometricRequested;
-    private final int mBiometricStrengthRequested;
-
+    private static final String TAG = "BiometricService/PreAuthInfo";
     final boolean credentialRequested;
     // Sensors that can be used for this request (e.g. strong enough, enrolled, enabled).
     final List<BiometricSensor> eligibleSensors;
@@ -90,6 +71,25 @@
     final boolean ignoreEnrollmentState;
     final int userId;
     final Context context;
+    private final boolean mBiometricRequested;
+    private final int mBiometricStrengthRequested;
+    private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
+            boolean credentialRequested, List<BiometricSensor> eligibleSensors,
+            List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
+            boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
+            Context context) {
+        mBiometricRequested = biometricRequested;
+        mBiometricStrengthRequested = biometricStrengthRequested;
+        this.credentialRequested = credentialRequested;
+
+        this.eligibleSensors = eligibleSensors;
+        this.ineligibleSensors = ineligibleSensors;
+        this.credentialAvailable = credentialAvailable;
+        this.confirmationRequested = confirmationRequested;
+        this.ignoreEnrollmentState = ignoreEnrollmentState;
+        this.userId = userId;
+        this.context = context;
+    }
 
     static PreAuthInfo create(ITrustManager trustManager,
             DevicePolicyManager devicePolicyManager,
@@ -158,7 +158,8 @@
      *
      * @return @AuthenticatorStatus
      */
-    private static @AuthenticatorStatus int getStatusForBiometricAuthenticator(
+    private static @AuthenticatorStatus
+    int getStatusForBiometricAuthenticator(
             DevicePolicyManager devicePolicyManager,
             BiometricService.SettingObserver settingObserver,
             BiometricSensor sensor, int userId, String opPackageName,
@@ -200,7 +201,6 @@
                 }
             }
 
-
             final @LockoutTracker.LockoutMode int lockoutMode =
                     sensor.impl.getLockoutModeForUser(userId);
             if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
@@ -248,8 +248,8 @@
 
     /**
      * @param modality one of {@link BiometricAuthenticator#TYPE_FINGERPRINT},
-     * {@link BiometricAuthenticator#TYPE_IRIS} or {@link BiometricAuthenticator#TYPE_FACE}
-     * @return
+     *                 {@link BiometricAuthenticator#TYPE_IRIS} or
+     *                 {@link BiometricAuthenticator#TYPE_FACE}
      */
     private static int mapModalityToDevicePolicyType(int modality) {
         switch (modality) {
@@ -265,24 +265,6 @@
         }
     }
 
-    private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
-            boolean credentialRequested, List<BiometricSensor> eligibleSensors,
-            List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
-            boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
-            Context context) {
-        mBiometricRequested = biometricRequested;
-        mBiometricStrengthRequested = biometricStrengthRequested;
-        this.credentialRequested = credentialRequested;
-
-        this.eligibleSensors = eligibleSensors;
-        this.ineligibleSensors = ineligibleSensors;
-        this.credentialAvailable = credentialAvailable;
-        this.confirmationRequested = confirmationRequested;
-        this.ignoreEnrollmentState = ignoreEnrollmentState;
-        this.userId = userId;
-        this.context = context;
-    }
-
     private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
         // If the caller requested STRONG, and the device contains both STRONG and non-STRONG
         // sensors, prioritize BIOMETRIC_NOT_ENROLLED over the weak sensor's
@@ -303,6 +285,7 @@
      * surface, combined with the actual sensor/credential and user/system settings, calculate the
      * internal {@link AuthenticatorStatus} that should be returned to the client. Note that this
      * will need to be converted into the public API constant.
+     *
      * @return Pair<Modality, Error> with error being the internal {@link AuthenticatorStatus} code
      */
     private Pair<Integer, Integer> getInternalStatus() {
@@ -391,7 +374,8 @@
     /**
      * @return public BiometricManager result for the current request.
      */
-    @BiometricManager.BiometricError int getCanAuthenticateResult() {
+    @BiometricManager.BiometricError
+    int getCanAuthenticateResult() {
         // TODO: Convert this directly
         return Utils.biometricConstantsToBiometricManager(
                 Utils.authenticatorStatusToBiometricConstant(
@@ -401,6 +385,7 @@
     /**
      * For the given request, generate the appropriate reason why authentication cannot be started.
      * Note that for some errors, modality is intentionally cleared.
+     *
      * @return Pair<Modality, Error> with modality being filtered if necessary, and error
      * being one of the public {@link android.hardware.biometrics.BiometricConstants} codes.
      */
@@ -443,7 +428,8 @@
      * @return bitmask representing the modalities that are running or could be running for the
      * current session.
      */
-    @BiometricAuthenticator.Modality int getEligibleModalities() {
+    @BiometricAuthenticator.Modality
+    int getEligibleModalities() {
         @BiometricAuthenticator.Modality int modalities = 0;
         for (BiometricSensor sensor : eligibleSensors) {
             modalities |= sensor.modality;
@@ -474,7 +460,7 @@
                         + ", StrengthRequested: " + mBiometricStrengthRequested
                         + ", CredentialRequested: " + credentialRequested);
         string.append(", Eligible:{");
-        for (BiometricSensor sensor: eligibleSensors) {
+        for (BiometricSensor sensor : eligibleSensors) {
             string.append(sensor.id).append(" ");
         }
         string.append("}");
@@ -489,4 +475,20 @@
         string.append(", ");
         return string.toString();
     }
+
+    @IntDef({AUTHENTICATOR_OK,
+            BIOMETRIC_NO_HARDWARE,
+            BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
+            BIOMETRIC_INSUFFICIENT_STRENGTH,
+            BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
+            BIOMETRIC_HARDWARE_NOT_DETECTED,
+            BIOMETRIC_NOT_ENROLLED,
+            BIOMETRIC_NOT_ENABLED_FOR_APPS,
+            CREDENTIAL_NOT_ENROLLED,
+            BIOMETRIC_LOCKOUT_TIMED,
+            BIOMETRIC_LOCKOUT_PERMANENT,
+            BIOMETRIC_SENSOR_PRIVACY_ENABLED})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AuthenticatorStatus {
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 23b2714..d456736 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -44,7 +44,7 @@
 /**
  * A default provider for {@link BiometricContext}.
  */
-final class BiometricContextProvider implements BiometricContext {
+public final class BiometricContextProvider implements BiometricContext {
 
     private static final String TAG = "BiometricContextProvider";
 
@@ -83,7 +83,8 @@
     private boolean mIsAwake = false;
 
     @VisibleForTesting
-    BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
+    public BiometricContextProvider(
+            @NonNull AmbientDisplayConfiguration ambientDisplayConfiguration,
             @NonNull IStatusBarService service, @Nullable Handler handler,
             AuthSessionCoordinator authSessionCoordinator) {
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
index bdae5f3..a48a9d1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthResultCoordinator.java
@@ -75,7 +75,10 @@
      * Adds auth success for a given strength to the current operation list.
      */
     void authenticatedFor(@Authenticators.Types int strength) {
-        updateState(strength, (old) -> AUTHENTICATOR_UNLOCKED | old);
+        // Only strong unlocks matter.
+        if (strength == Authenticators.BIOMETRIC_STRONG) {
+            updateState(strength, (old) -> AUTHENTICATOR_UNLOCKED | old);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
index 5bc9d23..1aee5d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
@@ -75,6 +75,7 @@
         mUserId = userId;
         mIsAuthenticating = true;
         mAuthOperations.clear();
+        mTimedLockouts.clear();
         mAuthResultCoordinator = new AuthResultCoordinator();
         mRingBuffer.addApiCall("internal : onAuthSessionStarted(" + userId + ")");
     }
@@ -88,7 +89,6 @@
      */
     void endAuthSession() {
         if (mIsAuthenticating) {
-            mAuthOperations.clear();
             final long currentTime = mClock.millis();
             for (Pair<Integer, Long> timedLockouts : mTimedLockouts) {
                 mMultiBiometricLockoutState.increaseLockoutTime(mUserId, timedLockouts.first,
@@ -109,16 +109,24 @@
                 }
 
             }
+
             mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")");
-            mIsAuthenticating = false;
+            clearSession();
         }
     }
 
+    private void clearSession() {
+        mIsAuthenticating = false;
+        mTimedLockouts.clear();
+        mAuthOperations.clear();
+    }
+
     /**
-     * @return true if a user can authenticate with a given strength.
+     * Returns the current lockout state for a given user/strength.
      */
-    public boolean getCanAuthFor(int userId, @Authenticators.Types int strength) {
-        return mMultiBiometricLockoutState.canUserAuthenticate(userId, strength);
+    @LockoutTracker.LockoutMode
+    public int getLockoutStateFor(int userId, @Authenticators.Types int strength) {
+        return mMultiBiometricLockoutState.getLockoutState(userId, strength);
     }
 
     @Override
@@ -145,19 +153,8 @@
     }
 
     @Override
-    public void authenticatedFor(int userId, @Authenticators.Types int biometricStrength,
-            int sensorId, long requestId) {
-        final String authStr =
-                "authenticatedFor(userId=" + userId + ", strength=" + biometricStrength
-                        + " , sensorId=" + sensorId + ", requestId= " + requestId + ")";
-        mRingBuffer.addApiCall(authStr);
-        mAuthResultCoordinator.authenticatedFor(biometricStrength);
-        attemptToFinish(userId, sensorId, authStr);
-    }
-
-    @Override
-    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength,
-            int sensorId, long requestId) {
+    public void lockedOutFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
+            long requestId) {
         final String lockedOutStr =
                 "lockOutFor(userId=" + userId + ", biometricStrength=" + biometricStrength
                         + ", sensorId=" + sensorId + ", requestId=" + requestId + ")";
@@ -179,12 +176,16 @@
     }
 
     @Override
-    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength,
-            int sensorId, long requestId) {
+    public void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
+            long requestId, boolean wasSuccessful) {
         final String authEndedStr =
                 "authEndedFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
-                        + ", sensorId=" + sensorId + ", requestId=" + requestId + ")";
+                        + ", sensorId=" + sensorId + ", requestId=" + requestId + ", wasSuccessful="
+                        + wasSuccessful + ")";
         mRingBuffer.addApiCall(authEndedStr);
+        if (wasSuccessful) {
+            mAuthResultCoordinator.authenticatedFor(biometricStrength);
+        }
         attemptToFinish(userId, sensorId, authEndedStr);
     }
 
@@ -195,6 +196,12 @@
                 "resetLockoutFor(userId=" + userId + " ,biometricStrength=" + biometricStrength
                         + ", requestId=" + requestId + ")";
         mRingBuffer.addApiCall(resetLockStr);
+        if (biometricStrength == Authenticators.BIOMETRIC_STRONG) {
+            clearSession();
+        } else {
+            // Lockouts cannot be reset by non-strong biometrics
+            return;
+        }
         mMultiBiometricLockoutState.setAuthenticatorTo(userId, biometricStrength,
                 true /*canAuthenticate */);
         mMultiBiometricLockoutState.clearLockoutTime(userId, biometricStrength);
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
index d97f793..6bddf14 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionListener.java
@@ -28,16 +28,10 @@
     void authStartedFor(int userId, int sensorId, long requestId);
 
     /**
-     * Indicates a successful authentication occurred for a sensor of a given strength.
-     */
-    void authenticatedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
-            long requestId);
-
-    /**
      * Indicates authentication ended for a sensor of a given strength.
      */
     void authEndedFor(int userId, @Authenticators.Types int biometricStrength, int sensorId,
-            long requestId);
+            long requestId, boolean wasSuccessful);
 
     /**
      * Indicates a lockout occurred for a sensor of a given strength.
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 8a24ff6..57d28f9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -81,9 +81,12 @@
     @State
     protected int mState = STATE_NEW;
     private long mStartTimeMs;
-
     private boolean mAuthAttempted;
     private boolean mAuthSuccess = false;
+    private final int mSensorStrength;
+    // This is used to determine if we should use the old lockout counter (HIDL) or the new lockout
+    // counter implementation (AIDL)
+    private final boolean mShouldUseLockoutTracker;
 
     public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -92,7 +95,7 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
-            boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
+            boolean shouldVibrate, boolean isKeyguardBypassEnabled, int sensorStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
                 shouldVibrate, biometricLogger, biometricContext);
         mIsStrongBiometric = isStrongBiometric;
@@ -105,22 +108,13 @@
         mIsRestricted = restricted;
         mAllowBackgroundAuthentication = allowBackgroundAuthentication;
         mIsKeyguardBypassEnabled = isKeyguardBypassEnabled;
+        mShouldUseLockoutTracker = lockoutTracker != null;
+        mSensorStrength = sensorStrength;
     }
 
     @LockoutTracker.LockoutMode
     public int handleFailedAttempt(int userId) {
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                mLockoutTracker.getLockoutModeForUser(userId);
-        final PerformanceTracker performanceTracker =
-                PerformanceTracker.getInstanceForSensorId(getSensorId());
-
-        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
-            performanceTracker.incrementPermanentLockoutForUser(userId);
-        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
-            performanceTracker.incrementTimedLockoutForUser(userId);
-        }
-
-        return lockoutMode;
+        return LockoutTracker.LOCKOUT_NONE;
     }
 
     protected long getStartTimeMs() {
@@ -273,10 +267,12 @@
                 cancel();
             } else {
                 // Allow system-defined limit of number of attempts before giving up
-                @LockoutTracker.LockoutMode final int lockoutMode =
-                        handleFailedAttempt(getTargetUserId());
-                if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
-                    markAlreadyDone();
+                if (mShouldUseLockoutTracker) {
+                    @LockoutTracker.LockoutMode final int lockoutMode =
+                            handleFailedAttempt(getTargetUserId());
+                    if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+                        markAlreadyDone();
+                    }
                 }
 
                 try {
@@ -309,13 +305,6 @@
     @Override
     public void onAcquired(int acquiredInfo, int vendorCode) {
         super.onAcquired(acquiredInfo, vendorCode);
-
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                mLockoutTracker.getLockoutModeForUser(getTargetUserId());
-        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
-            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
-            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
-        }
     }
 
     @Override
@@ -331,8 +320,14 @@
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
-        @LockoutTracker.LockoutMode final int lockoutMode =
-                mLockoutTracker.getLockoutModeForUser(getTargetUserId());
+        final @LockoutTracker.LockoutMode int lockoutMode;
+        if (mShouldUseLockoutTracker) {
+            lockoutMode = mLockoutTracker.getLockoutModeForUser(getTargetUserId());
+        } else {
+            lockoutMode = getBiometricContext().getAuthSessionCoordinator()
+                    .getLockoutStateFor(getTargetUserId(), mSensorStrength);
+        }
+
         if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
             Slog.v(TAG, "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
             int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
@@ -406,6 +401,14 @@
         return mAuthSuccess;
     }
 
+    protected int getSensorStrength() {
+        return mSensorStrength;
+    }
+
+    protected LockoutTracker getLockoutTracker() {
+        return mLockoutTracker;
+    }
+
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
diff --git a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
index 6605d49..c24a989 100644
--- a/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/MultiBiometricLockoutState.java
@@ -75,6 +75,9 @@
                 // fall through
             case Authenticators.BIOMETRIC_CONVENIENCE:
                 authMap.get(BIOMETRIC_CONVENIENCE).mPermanentlyLockedOut = !canAuth;
+                return;
+            default:
+                Slog.e(TAG, "increaseLockoutTime called for invalid strength : "  + strength);
         }
     }
 
@@ -89,6 +92,9 @@
                 // fall through
             case Authenticators.BIOMETRIC_CONVENIENCE:
                 authMap.get(BIOMETRIC_CONVENIENCE).increaseLockoutTo(duration);
+                return;
+            default:
+                Slog.e(TAG, "increaseLockoutTime called for invalid strength : "  + strength);
         }
     }
 
@@ -103,22 +109,34 @@
                 // fall through
             case Authenticators.BIOMETRIC_CONVENIENCE:
                 authMap.get(BIOMETRIC_CONVENIENCE).setTimedLockout(0);
+                return;
+            default:
+                Slog.e(TAG, "clearLockoutTime called for invalid strength : "  + strength);
         }
     }
 
     /**
-     * Indicates if a user can perform an authentication operation with a given
-     * {@link Authenticators.Types}
+     * Retrieves the lockout state for a user of a specified strength.
      *
      * @param userId   The user.
      * @param strength The strength of biometric that is requested to authenticate.
-     * @return If a user can authenticate with a given biometric of this strength.
      */
-    boolean canUserAuthenticate(int userId, @Authenticators.Types int strength) {
-        final boolean canAuthenticate = getAuthMapForUser(userId).get(strength).canAuthenticate();
-        Slog.d(TAG, "canUserAuthenticate(userId=" + userId + ", strength=" + strength + ") ="
-                + canAuthenticate);
-        return canAuthenticate;
+    @LockoutTracker.LockoutMode
+    int getLockoutState(int userId, @Authenticators.Types int strength) {
+        final Map<Integer, AuthenticatorState> authMap = getAuthMapForUser(userId);
+        if (!authMap.containsKey(strength)) {
+            Slog.e(TAG, "Error, getLockoutState for unknown strength: " + strength
+                    + " returning LOCKOUT_NONE");
+            return LockoutTracker.LOCKOUT_NONE;
+        }
+        final AuthenticatorState state = authMap.get(strength);
+        if (state.mPermanentlyLockedOut) {
+            return LockoutTracker.LOCKOUT_PERMANENT;
+        } else if (state.isTimedLockout()) {
+            return LockoutTracker.LOCKOUT_TIMED;
+        } else {
+            return LockoutTracker.LOCKOUT_NONE;
+        }
     }
 
     @Override
@@ -152,7 +170,15 @@
         }
 
         boolean canAuthenticate() {
-            return !mPermanentlyLockedOut && mClock.millis() - mTimedLockout >= 0;
+            return !mPermanentlyLockedOut && !isTimedLockout();
+        }
+
+        boolean isTimedLockout() {
+            return mClock.millis() - mTimedLockout < 0;
+        }
+
+        void setTimedLockout(long duration) {
+            mTimedLockout = duration;
         }
 
         /**
@@ -162,10 +188,6 @@
             mTimedLockout = Math.max(mTimedLockout, duration);
         }
 
-        void setTimedLockout(long duration) {
-            mTimedLockout = duration;
-        }
-
         String toString(long currentTime) {
             final String duration =
                     mTimedLockout - currentTime > 0 ? (mTimedLockout - currentTime) + "ms" : "none";
diff --git a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
index 42b22b0..eed2bdd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/PerformanceTracker.java
@@ -85,7 +85,7 @@
         }
     }
 
-    void incrementAcquireForUser(int userId, boolean isCrypto) {
+    public void incrementAcquireForUser(int userId, boolean isCrypto) {
         createUserEntryIfNecessary(userId);
 
         if (isCrypto) {
@@ -95,13 +95,13 @@
         }
     }
 
-    void incrementTimedLockoutForUser(int userId) {
+    public void incrementTimedLockoutForUser(int userId) {
         createUserEntryIfNecessary(userId);
 
         mAllUsersInfo.get(userId).mTimedLockout++;
     }
 
-    void incrementPermanentLockoutForUser(int userId) {
+    public void incrementPermanentLockoutForUser(int userId) {
         createUserEntryIfNecessary(userId);
 
         mAllUsersInfo.get(userId).mPermanentLockout++;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 33d3b64..2f147c4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -200,6 +200,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void scheduleWatchdog() {
+            super.scheduleWatchdog_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for scheduling watchdog");
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index c27d71f..b1cb257 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -47,7 +47,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
-import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import java.util.ArrayList;
@@ -63,8 +63,6 @@
     @NonNull
     private final UsageStats mUsageStats;
     @NonNull
-    private final LockoutCache mLockoutCache;
-    @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable
     private final NotificationManager mNotificationManager;
@@ -72,7 +70,6 @@
     private final int[] mBiometricPromptIgnoreListVendor;
     private final int[] mKeyguardIgnoreList;
     private final int[] mKeyguardIgnoreListVendor;
-    private final int mBiometricStrength;
     @Nullable
     private ICancellationSignal mCancellationSignal;
     @Nullable
@@ -88,12 +85,12 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull UsageStats usageStats,
             @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
-            boolean isKeyguardBypassEnabled, @Authenticators.Types int biometricStrength) {
+            boolean isKeyguardBypassEnabled, @Authenticators.Types int sensorStrength) {
         this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId,
                 restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
-                isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class),
-                biometricStrength);
+                isStrongBiometric, usageStats, lockoutCache /* lockoutCache */,
+                allowBackgroundAuthentication, isKeyguardBypassEnabled,
+                context.getSystemService(SensorPrivacyManager.class), sensorStrength);
     }
 
     @VisibleForTesting
@@ -109,13 +106,11 @@
             @Authenticators.Types int biometricStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, null /* taskStackListener */, lockoutCache,
-                allowBackgroundAuthentication,
-                false /* shouldVibrate */,
-                isKeyguardBypassEnabled);
+                isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
+                allowBackgroundAuthentication, false /* shouldVibrate */,
+                isKeyguardBypassEnabled, biometricStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
-        mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mSensorPrivacyManager = sensorPrivacyManager;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
@@ -129,14 +124,12 @@
                 R.array.config_face_acquire_keyguard_ignorelist);
         mKeyguardIgnoreListVendor = resources.getIntArray(
                 R.array.config_face_acquire_vendor_keyguard_ignorelist);
-        mBiometricStrength = biometricStrength;
     }
 
     @Override
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         mState = STATE_STARTED;
-        mAuthSessionCoordinator.authStartedFor(getTargetUserId(), getSensorId(), getRequestId());
     }
 
     @NonNull
@@ -221,9 +214,6 @@
                 0 /* error */,
                 0 /* vendorError */,
                 getTargetUserId()));
-        mAuthSessionCoordinator
-                .authenticatedFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                        getRequestId());
     }
 
     @Override
@@ -239,8 +229,6 @@
         if (error == BiometricConstants.BIOMETRIC_ERROR_RE_ENROLL) {
             BiometricNotificationUtils.showReEnrollmentNotification(getContext());
         }
-        mAuthSessionCoordinator.authEndedFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
         super.onError(error, vendorCode);
     }
 
@@ -263,6 +251,8 @@
         mLastAcquire = acquireInfo;
         final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
+        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
     }
 
     /**
@@ -290,35 +280,39 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
+        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), getSensorStrength(), getSensorId(),
+                durationMillis, getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
         getLogger().logOnError(getContext(), getOperationContext(),
                 error, 0 /* vendorCode */, getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementTimedLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
-        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), mBiometricStrength, getSensorId(),
-                durationMillis, getRequestId());
     }
 
     @Override
     public void onLockoutPermanent() {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
+        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), getSensorStrength(), getSensorId(),
+                getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
         getLogger().logOnError(getContext(), getOperationContext(),
                 error, 0 /* vendorCode */, getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementPermanentLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
-        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 6488185..89852a1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -50,10 +50,11 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -82,20 +83,34 @@
 
     private boolean mTestHalEnabled;
 
-    @NonNull private final Context mContext;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final String mHalInstanceName;
-    @NonNull @VisibleForTesting
+    @NonNull
+    @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
-    @NonNull private final Handler mHandler;
-    @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-    @NonNull private final UsageStats mUsageStats;
-    @NonNull private final ActivityTaskManager mActivityTaskManager;
-    @NonNull private final BiometricTaskStackListener mTaskStackListener;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final String mHalInstanceName;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull
+    private final UsageStats mUsageStats;
+    @NonNull
+    private final ActivityTaskManager mActivityTaskManager;
+    @NonNull
+    private final BiometricTaskStackListener mTaskStackListener;
     // for requests that do not use biometric prompt
-    @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
-    @NonNull private final BiometricContext mBiometricContext;
-    @Nullable private IFace mDaemon;
+    @NonNull
+    private final AtomicLong mRequestCounter = new AtomicLong(0);
+    @NonNull
+    private final BiometricContext mBiometricContext;
+    @NonNull
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @Nullable
+    private IFace mDaemon;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -141,6 +156,7 @@
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
+        mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
@@ -312,7 +328,8 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(sensorId));
     }
 
     @Override
@@ -423,7 +440,6 @@
             boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
-            final int biometricStrength = Utils.getCurrentStrength(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
                     mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
                     userId, operationId, restricted, opPackageName, cookie,
@@ -431,8 +447,24 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
-                    allowBackgroundAuthentication, isKeyguardBypassEnabled, biometricStrength);
-            scheduleForSensor(sensorId, client, mBiometricStateCallback);
+                    allowBackgroundAuthentication, isKeyguardBypassEnabled,
+                    Utils.getCurrentStrength(sensorId)
+                    );
+            scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
+                @Override
+                public void onClientStarted(
+                         BaseClientMonitor clientMonitor) {
+                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                }
+
+                @Override
+                public void onClientFinished(
+                        BaseClientMonitor clientMonitor,
+                        boolean success) {
+                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
+                            sensorId, requestId, success);
+                }
+            });
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 32bed48..759c52a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -89,9 +89,9 @@
     void onLockoutCleared() {
         resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
                 mLockoutResetDispatcher);
+        mCallback.onClientFinished(this, true /* success */);
         getBiometricContext().getAuthSessionCoordinator()
                 .resetLockoutFor(getTargetUserId(), mBiometricStrength, getRequestId());
-        mCallback.onClientFinished(this, true /* success */);
     }
 
     public boolean interruptsPrecedingClients() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 0f5cdc3..0d30ddd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -637,7 +637,7 @@
 
     public void onBinderDied() {
         final BaseClientMonitor client = mScheduler.getCurrentClient();
-        if (client.isInterruptable()) {
+        if (client != null && client.isInterruptable()) {
             Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
             final ErrorConsumer errorConsumer = (ErrorConsumer) client;
             errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 0e0ee19..1adc5e3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -677,8 +677,9 @@
                     opPackageName, cookie, false /* requireConfirmation */, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
-                    mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
-            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+                    mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled,
+                    Utils.getCurrentStrength(mSensorId));
+            mScheduler.scheduleClientMonitor(client);
         });
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 91eec7d..d4a7f08 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
@@ -39,6 +40,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import java.util.ArrayList;
@@ -70,12 +72,12 @@
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
             @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
-            boolean isKeyguardBypassEnabled) {
+            boolean isKeyguardBypassEnabled, @Authenticators.Types int sensorStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
                 isStrongBiometric, null /* taskStackListener */,
                 lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
-                isKeyguardBypassEnabled);
+                isKeyguardBypassEnabled, sensorStrength);
         setRequestId(requestId);
         mUsageStats = usageStats;
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
@@ -154,6 +156,21 @@
     }
 
     @Override
+    public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(userId);
+        final PerformanceTracker performanceTracker =
+                PerformanceTracker.getInstanceForSensorId(getSensorId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+            performanceTracker.incrementPermanentLockoutForUser(userId);
+        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+            performanceTracker.incrementTimedLockoutForUser(userId);
+        }
+
+        return lockoutMode;
+    }
+
+    @Override
     public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> token) {
         super.onAuthenticated(identifier, authenticated, token);
@@ -204,6 +221,12 @@
         if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) {
             BiometricNotificationUtils.showReEnrollmentNotification(getContext());
         }
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
+            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+        }
 
         final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 70470e9..48367b2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -960,6 +960,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void scheduleWatchdog() {
+            super.scheduleWatchdog_enforcePermission();
+
             final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
             if (provider == null) {
                 Slog.w(TAG, "Null provider for scheduling watchdog");
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 7f1fb1c..5f38a92 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -56,7 +56,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
-import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -72,12 +72,9 @@
 class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
-    private static final int MESSAGE_IGNORE_AUTH = 1;
     private static final int MESSAGE_AUTH_SUCCESS = 2;
     private static final int MESSAGE_FINGER_UP = 3;
     @NonNull
-    private final LockoutCache mLockoutCache;
-    @NonNull
     private final SensorOverlays mSensorOverlays;
     @NonNull
     private final FingerprintSensorPropertiesInternal mSensorProps;
@@ -86,7 +83,6 @@
     private final Handler mHandler;
     private final int mSkipWaitForPowerAcquireMessage;
     private final int mSkipWaitForPowerVendorAcquireMessage;
-    private final int mBiometricStrength;
     private final long mFingerUpIgnoresPower = 500;
     private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable
@@ -141,12 +137,12 @@
                 biometricContext,
                 isStrongBiometric,
                 taskStackListener,
-                lockoutCache,
+                null /* lockoutCache */,
                 allowBackgroundAuthentication,
                 false /* shouldVibrate */,
-                false /* isKeyguardBypassEnabled */);
+                false /* isKeyguardBypassEnabled */,
+                biometricStrength);
         setRequestId(requestId);
-        mLockoutCache = lockoutCache;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController,
                 sidefpsController, udfpsOverlay);
         mSensorProps = sensorProps;
@@ -167,7 +163,6 @@
         mSkipWaitForPowerVendorAcquireMessage =
                 context.getResources().getInteger(
                         R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
-        mBiometricStrength = biometricStrength;
         mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
         mSideFpsLastAcquireStartTime = -1;
         mClock = clock;
@@ -197,8 +192,6 @@
         } else {
             mState = STATE_STARTED;
         }
-        mAuthSessionCoordinator.authStartedFor(getTargetUserId(), getSensorId(),
-                getRequestId());
     }
 
     @NonNull
@@ -212,8 +205,6 @@
     protected void handleLifecycleAfterAuth(boolean authenticated) {
         if (authenticated) {
             mCallback.onClientFinished(this, true /* success */);
-            mAuthSessionCoordinator.authenticatedFor(
-                    getTargetUserId(), mBiometricStrength, getSensorId(), getRequestId());
         }
     }
 
@@ -249,12 +240,6 @@
                 () -> {
                     long delay = 0;
                     if (authenticated && mSensorProps.isAnySidefpsType()) {
-                        if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
-                            Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
-                            onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
-                                    0, true);
-                            return;
-                        }
                         delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
 
                         if (mSideFpsLastAcquireStartTime != -1) {
@@ -311,6 +296,8 @@
                 });
             }
         }
+        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
 
     }
 
@@ -323,8 +310,6 @@
         }
 
         mSensorOverlays.hide(getSensorId());
-        mAuthSessionCoordinator.authEndedFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
     }
 
     @Override
@@ -462,7 +447,8 @@
 
     @Override
     public void onLockoutTimed(long durationMillis) {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
+        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), getSensorStrength(), getSensorId(),
+                durationMillis, getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
         getLogger()
@@ -473,6 +459,9 @@
                         0 /* vendorCode */,
                         getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementTimedLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
@@ -481,13 +470,12 @@
 
         mSensorOverlays.hide(getSensorId());
         mCallback.onClientFinished(this, false /* success */);
-        mAuthSessionCoordinator.lockOutTimed(getTargetUserId(), mBiometricStrength, getSensorId(),
-                durationMillis, getRequestId());
     }
 
     @Override
     public void onLockoutPermanent() {
-        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
+        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), getSensorStrength(), getSensorId(),
+                getRequestId());
         // Lockout metrics are logged as an error code.
         final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
         getLogger()
@@ -498,6 +486,9 @@
                         0 /* vendorCode */,
                         getTargetUserId());
 
+        PerformanceTracker.getInstanceForSensorId(getSensorId())
+                .incrementPermanentLockoutForUser(getTargetUserId());
+
         try {
             getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
         } catch (RemoteException e) {
@@ -506,8 +497,6 @@
 
         mSensorOverlays.hide(getSensorId());
         mCallback.onClientFinished(this, false /* success */);
-        mAuthSessionCoordinator.lockedOutFor(getTargetUserId(), mBiometricStrength, getSensorId(),
-                getRequestId());
     }
 
     @Override
@@ -515,18 +504,13 @@
         if (mSensorProps.isAnySidefpsType()) {
             Slog.i(TAG, "(sideFPS): onPowerPressed");
             mHandler.post(() -> {
-                if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
-                    Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
-                    mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
-                    // Do not call onError() as that will send an additional callback to coex.
-                    onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
-                    mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
-                            mBiometricStrength, getSensorId(), getRequestId());
-                }
-                mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
-                mHandler.postDelayed(() -> {
-                }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+                Slog.i(TAG, "(sideFPS): finishing auth");
+                // Ignore auths after a power has been detected
+                mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+                // Do not call onError() as that will send an additional callback to coex.
+                onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+                        0, true);
+                mSensorOverlays.hide(getSensorId());
             });
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index a42ff9a..b42b1c6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -58,6 +58,7 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -94,15 +95,23 @@
 
     private boolean mTestHalEnabled;
 
-    @NonNull private final Context mContext;
-    @NonNull private final BiometricStateCallback mBiometricStateCallback;
-    @NonNull private final String mHalInstanceName;
-    @NonNull @VisibleForTesting
+    @NonNull
+    @VisibleForTesting
     final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
-    @NonNull private final Handler mHandler;
-    @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-    @NonNull private final ActivityTaskManager mActivityTaskManager;
-    @NonNull private final BiometricTaskStackListener mTaskStackListener;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull
+    private final String mHalInstanceName;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull
+    private final ActivityTaskManager mActivityTaskManager;
+    @NonNull
+    private final BiometricTaskStackListener mTaskStackListener;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     @NonNull private final BiometricContext mBiometricContext;
@@ -110,6 +119,7 @@
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
     @Nullable private IUdfpsOverlay mUdfpsOverlay;
+    private AuthSessionCoordinator mAuthSessionCoordinator;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -154,6 +164,7 @@
         mActivityTaskManager = ActivityTaskManager.getInstance();
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
+        mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
 
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
@@ -179,11 +190,11 @@
                             true /* resetLockoutRequiresHardwareAuthToken */,
                             !workaroundLocations.isEmpty() ? workaroundLocations :
                                     Arrays.stream(prop.sensorLocations).map(location ->
-                                            new SensorLocationInternal(
-                                                    location.display,
-                                                    location.sensorLocationX,
-                                                    location.sensorLocationY,
-                                                    location.sensorRadius))
+                                                    new SensorLocationInternal(
+                                                            location.display,
+                                                            location.sensorLocationX,
+                                                            location.sensorLocationY,
+                                                            location.sensorRadius))
                                             .collect(Collectors.toList()));
             final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
                     internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
@@ -347,7 +358,7 @@
                             mSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN),
                             mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -445,8 +456,30 @@
                     mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
                     allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties(), mHandler,
-                    Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock());
-            scheduleForSensor(sensorId, client, mBiometricStateCallback);
+                    Utils.getCurrentStrength(sensorId),
+                    SystemClock.elapsedRealtimeClock());
+            scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
+
+                @Override
+                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+                    mBiometricStateCallback.onClientStarted(clientMonitor);
+                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                }
+
+                @Override
+                public void onBiometricAction(int action) {
+                    mBiometricStateCallback.onBiometricAction(action);
+                }
+
+                @Override
+                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+                        boolean success) {
+                    mBiometricStateCallback.onClientFinished(clientMonitor, success);
+                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
+                            sensorId, requestId, success);
+                }
+            });
+
         });
     }
 
@@ -584,7 +617,8 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
+        return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+                Utils.getCurrentStrength(sensorId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 22f504c..0b2421b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -93,9 +93,9 @@
     void onLockoutCleared() {
         resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
                 mLockoutResetDispatcher);
+        mCallback.onClientFinished(this, true /* success */);
         getBiometricContext().getAuthSessionCoordinator()
                 .resetLockoutFor(getTargetUserId(), mBiometricStrength, getRequestId());
-        mCallback.onClientFinished(this, true /* success */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index dbc96df..1f30363 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -667,7 +667,8 @@
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
-                    allowBackgroundAuthentication, mSensorProperties);
+                    allowBackgroundAuthentication, mSensorProperties,
+                    Utils.getCurrentStrength(mSensorId));
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 56fa36e..089317e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
@@ -42,6 +43,7 @@
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
 import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -79,12 +81,13 @@
             @Nullable ISidefpsController sidefpsController,
             @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
-            @NonNull FingerprintSensorPropertiesInternal sensorProps) {
+            @NonNull FingerprintSensorPropertiesInternal sensorProps,
+            @Authenticators.Types int sensorStrength) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, taskStackListener, lockoutTracker,
-                allowBackgroundAuthentication, false /* shouldVibrate */,
-                false /* isKeyguardBypassEnabled */);
+                isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
+                false /* shouldVibrate */, false /* isKeyguardBypassEnabled */,
+                sensorStrength);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
         mSensorOverlays = new SensorOverlays(udfpsOverlayController,
@@ -167,6 +170,18 @@
     }
 
     @Override
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        super.onAcquired(acquiredInfo, vendorCode);
+
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
+            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+        }
+    }
+
+    @Override
     public boolean wasUserDetected() {
         // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
         return false;
@@ -175,7 +190,17 @@
     @Override
     public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
         mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
-        return super.handleFailedAttempt(userId);
+        @LockoutTracker.LockoutMode final int lockoutMode =
+                getLockoutTracker().getLockoutModeForUser(userId);
+        final PerformanceTracker performanceTracker =
+                PerformanceTracker.getInstanceForSensorId(getSensorId());
+        if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+            performanceTracker.incrementPermanentLockoutForUser(userId);
+        } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+            performanceTracker.incrementTimedLockoutForUser(userId);
+        }
+
+        return lockoutMode;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c671a2c..0741d46 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -127,6 +127,8 @@
 import android.system.keystore2.KeyPermission;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Range;
 
@@ -296,6 +298,10 @@
         return mVpnProfileStore;
     }
 
+    private static final int MAX_EVENTS_LOGS = 20;
+    private final LocalLog mUnderlyNetworkChanges = new LocalLog(MAX_EVENTS_LOGS);
+    private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS);
+
     /**
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
      * only applies to {@link VpnService} connections.
@@ -841,6 +847,9 @@
             int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
             @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
             @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+        mVpnManagerEvents.log("Event class=" + getVpnManagerEventClassName(errorClass)
+                + ", err=" + getVpnManagerEventErrorName(errorCode) + " for " + packageName
+                + " on session " + sessionKey);
         final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
                 packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
         return sendEventToVpnManagerApp(intent, packageName);
@@ -1572,6 +1581,7 @@
                 ? Arrays.asList(mConfig.underlyingNetworks) : null);
 
         mNetworkCapabilities = capsBuilder.build();
+        logUnderlyNetworkChanges(mNetworkCapabilities.getUnderlyingNetworks());
         mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
                 mNetworkCapabilities, lp,
                 new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
@@ -1599,6 +1609,11 @@
         }
     }
 
+    private void logUnderlyNetworkChanges(List<Network> networks) {
+        mUnderlyNetworkChanges.log("Switch to "
+                + ((networks != null) ? TextUtils.join(", ", networks) : "null"));
+    }
+
     private void agentDisconnect(NetworkAgent networkAgent) {
         if (networkAgent != null) {
             networkAgent.unregister();
@@ -2928,7 +2943,6 @@
                     ikeConfiguration.isIkeExtensionEnabled(
                             IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
             onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
-            mRetryCount = 0;
         }
 
         /**
@@ -3048,6 +3062,7 @@
                 }
 
                 doSendLinkProperties(networkAgent, lp);
+                mRetryCount = 0;
             } catch (Exception e) {
                 Log.d(TAG, "Error in ChildOpened for token " + token, e);
                 onSessionLost(token, e);
@@ -3345,6 +3360,10 @@
         }
 
         private void scheduleRetryNewIkeSession() {
+            if (mScheduledHandleRetryIkeSessionFuture != null) {
+                Log.d(TAG, "There is a pending retrying task, skip the new retrying task");
+                return;
+            }
             final long retryDelay = mDeps.getNextRetryDelaySeconds(mRetryCount++);
             Log.d(TAG, "Retry new IKE session after " + retryDelay + " seconds.");
             // If the default network is lost during the retry delay, the mActiveNetwork will be
@@ -4368,6 +4387,7 @@
         // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
         //  ConnectivityServiceTest.
         if (SdkLevel.isAtLeastT()) {
+            mVpnManagerEvents.log(packageName + " stopped");
             sendEventToVpnManagerApp(intent, packageName);
         }
     }
@@ -4535,8 +4555,10 @@
     /** Proxy to allow different testing setups */
     // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
     // NetworkAgent#setUnderlyingNetworks can be un-finalized.
-    private static void doSetUnderlyingNetworks(
+    private void doSetUnderlyingNetworks(
             @NonNull NetworkAgent agent, @NonNull List<Network> networks) {
+        logUnderlyNetworkChanges(networks);
+
         if (agent instanceof VpnNetworkAgentWrapper) {
             ((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks);
         } else {
@@ -4655,4 +4677,57 @@
     static Range<Integer> createUidRangeForUser(int userId) {
         return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
     }
+
+    private String getVpnManagerEventClassName(int code) {
+        switch (code) {
+            case VpnManager.ERROR_CLASS_NOT_RECOVERABLE:
+                return "ERROR_CLASS_NOT_RECOVERABLE";
+            case VpnManager.ERROR_CLASS_RECOVERABLE:
+                return "ERROR_CLASS_RECOVERABLE";
+            default:
+                return "UNKNOWN_CLASS";
+        }
+    }
+
+    private String getVpnManagerEventErrorName(int code) {
+        switch (code) {
+            case VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST:
+                return "ERROR_CODE_NETWORK_UNKNOWN_HOST";
+            case VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT:
+                return "ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT";
+            case VpnManager.ERROR_CODE_NETWORK_IO:
+                return "ERROR_CODE_NETWORK_IO";
+            case VpnManager.ERROR_CODE_NETWORK_LOST:
+                return "ERROR_CODE_NETWORK_LOST";
+            default:
+                return "UNKNOWN_ERROR";
+        }
+    }
+
+    /** Dumps VPN state. */
+    public void dump(IndentingPrintWriter pw) {
+        synchronized (Vpn.this) {
+            pw.println("Active package name: " + mPackage);
+            pw.println("Active vpn type: " + getActiveVpnType());
+            pw.println("NetworkCapabilities: " + mNetworkCapabilities);
+            if (isIkev2VpnRunner()) {
+                final IkeV2VpnRunner runner = ((IkeV2VpnRunner) mVpnRunner);
+                pw.println("Token: " + runner.mSessionKey);
+                pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled"));
+                if (mDataStallSuspected) pw.println("Data stall suspected");
+                if (runner.mScheduledHandleDataStallFuture != null) {
+                    pw.println("Reset session scheduled");
+                }
+            }
+            pw.println("mUnderlyNetworkChanges (most recent first):");
+            pw.increaseIndent();
+            mUnderlyNetworkChanges.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println("mVpnManagerEvent (most recent first):");
+            pw.increaseIndent();
+            mVpnManagerEvents.reverseDump(pw);
+            pw.decreaseIndent();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 73afa60..eb81e70 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2215,7 +2215,8 @@
         pw.print("Storage low: "); pw.println(storageLowIntent != null);
         pw.print("Clock valid: "); pw.println(mSyncStorageEngine.isClockValid());
 
-        final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
+        final AccountAndUser[] accounts =
+                AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
 
         pw.print("Accounts: ");
         if (accounts != INITIAL_ACCOUNTS_ARRAY) {
@@ -3274,7 +3275,8 @@
         private void updateRunningAccountsH(EndPoint syncTargets) {
             synchronized (mAccountsLock) {
                 AccountAndUser[] oldAccounts = mRunningAccounts;
-                mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+                mRunningAccounts =
+                        AccountManagerService.getSingleton().getRunningAccountsForSystem();
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Slog.v(TAG, "Accounts list: ");
                     for (AccountAndUser acc : mRunningAccounts) {
@@ -3316,7 +3318,8 @@
             }
 
             // Cancel all jobs from non-existent accounts.
-            AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts();
+            AccountAndUser[] allAccounts =
+                    AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
             List<SyncOperation> ops = getAllPendingSyncs();
             for (int i = 0, opsSize = ops.size(); i < opsSize; i++) {
                 SyncOperation op = ops.get(i);
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index e9856d0..df4c471 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -1133,7 +1133,7 @@
 
         public void registerReceiver(Context context,
                 BroadcastReceiver receiver, IntentFilter filter) {
-            context.registerReceiver(receiver, filter);
+            context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         public void unregisterReceiver(Context context,
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e907ebf..d35d193 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1902,8 +1902,9 @@
                 if (displayDevice == null) {
                     return;
                 }
-                if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
-                        .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+                if (mLogicalDisplayMapper.getDisplayLocked(displayDevice) != null
+                        && mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+                        .getDisplayInfoLocked().type == Display.TYPE_INTERNAL && c != null) {
                     FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
                                 c.getCurve().first,
                                 c.getCurve().second,
@@ -2620,7 +2621,8 @@
             // initPowerManagement has not yet been called.
             return;
         }
-        if (mBrightnessTracker == null) {
+
+        if (mBrightnessTracker == null && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) {
             mBrightnessTracker = new BrightnessTracker(mContext, null);
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 726326f..a5e5c24 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -526,22 +526,28 @@
                         ranges, ranges);
             }
 
-            if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
-                    || primarySummary.disableRefreshRateSwitching) {
+            boolean modeSwitchingDisabled =
+                    mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
+                            || mModeSwitchingType
+                                == DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
+
+            if (modeSwitchingDisabled || primarySummary.disableRefreshRateSwitching) {
                 float fps = baseMode.getRefreshRate();
                 primarySummary.minPhysicalRefreshRate = primarySummary.maxPhysicalRefreshRate = fps;
-                if (mRenderFrameRateIsPhysicalRefreshRate) {
-                    primarySummary.minRenderFrameRate = primarySummary.maxRenderFrameRate = fps;
-                }
-                if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
-                    primarySummary.minRenderFrameRate = primarySummary.maxRenderFrameRate = fps;
+                if (modeSwitchingDisabled) {
                     appRequestSummary.minPhysicalRefreshRate =
                             appRequestSummary.maxPhysicalRefreshRate = fps;
-                    appRequestSummary.minRenderFrameRate =
-                            appRequestSummary.maxRenderFrameRate = fps;
                 }
             }
 
+            if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
+                    || mRenderFrameRateIsPhysicalRefreshRate) {
+                primarySummary.minRenderFrameRate = primarySummary.minPhysicalRefreshRate;
+                primarySummary.maxRenderFrameRate = primarySummary.maxPhysicalRefreshRate;
+                appRequestSummary.minRenderFrameRate = appRequestSummary.minPhysicalRefreshRate;
+                appRequestSummary.maxRenderFrameRate = appRequestSummary.maxPhysicalRefreshRate;
+            }
+
             boolean allowGroupSwitching =
                     mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
 
@@ -842,6 +848,8 @@
                 return "SWITCHING_TYPE_WITHIN_GROUPS";
             case DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS:
                 return "SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS";
+            case DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY:
+                return "SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY";
             default:
                 return "Unknown SwitchingType " + type;
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 422e98f..838bb53 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -927,7 +927,7 @@
 
         // Initialize all of the brightness tracking state
         final float brightness = convertToNits(mPowerState.getScreenBrightness());
-        if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+        if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
             mBrightnessTracker.start(brightness);
         }
         mBrightnessSettingListener = brightnessValue -> {
@@ -1059,7 +1059,9 @@
             }
 
             loadAmbientLightSensor();
-            if (mBrightnessTracker != null) {
+            // BrightnessTracker should only use one light sensor, we want to use the light sensor
+            // from the default display and not e.g. temporary displays when switching layouts.
+            if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
                 mBrightnessTracker.setLightSensor(mLightSensor);
             }
 
@@ -2485,7 +2487,7 @@
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
         if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
-                && mAutomaticBrightnessController != null) {
+                && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
             // nits and not using the arbitrary backlight units.
@@ -2838,18 +2840,22 @@
                 event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
                 ? -1f : convertToNits(event.getThermalMax());
 
-        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                convertToNits(event.getInitialBrightness()),
-                convertToNits(event.getBrightness()),
-                event.getSlowAmbientLux(),
-                event.getPhysicalDisplayId(),
-                event.isShortTermModelActive(),
-                appliedLowPowerMode,
-                appliedRbcStrength,
-                appliedHbmMaxNits,
-                appliedThermalCapNits,
-                event.isAutomaticBrightnessEnabled(),
-                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+                && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+                    .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+            FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                    convertToNits(event.getInitialBrightness()),
+                    convertToNits(event.getBrightness()),
+                    event.getSlowAmbientLux(),
+                    event.getPhysicalDisplayId(),
+                    event.isShortTermModelActive(),
+                    appliedLowPowerMode,
+                    appliedRbcStrength,
+                    appliedHbmMaxNits,
+                    appliedThermalCapNits,
+                    event.isAutomaticBrightnessEnabled(),
+                    FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+        }
     }
 
     private final class DisplayControllerHandler extends Handler {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3c1bf0b..d076b26 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -903,7 +903,7 @@
 
         // Initialize all of the brightness tracking state
         final float brightness = convertToNits(mPowerState.getScreenBrightness());
-        if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+        if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
             mBrightnessTracker.start(brightness);
         }
         mBrightnessSettingListener = brightnessValue -> {
@@ -1035,7 +1035,9 @@
             }
 
             loadAmbientLightSensor();
-            if (mBrightnessTracker != null) {
+            // BrightnessTracker should only use one light sensor, we want to use the light sensor
+            // from the default display and not e.g. temporary displays when switching layouts.
+            if (mBrightnessTracker != null && mDisplayId == Display.DEFAULT_DISPLAY) {
                 mBrightnessTracker.setLightSensor(mLightSensor);
             }
 
@@ -2439,7 +2441,7 @@
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
         if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
-                && mAutomaticBrightnessController != null) {
+                && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
             // nits and not using the arbitrary backlight units.
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4ca4817..87327cb 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -211,7 +211,7 @@
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
         mUiEventLogger = new UiEventLoggerImpl();
         mDreamUiEventLogger = new DreamUiEventLoggerImpl(
-                mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+                mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
         AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
         mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
         mDreamsOnlyEnabledForSystemUser =
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a..96ebcbb 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@
  * @hide
  */
 public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
-    final String mLoggableDreamPrefix;
+    private final String[] mLoggableDreamPrefixes;
 
-    DreamUiEventLoggerImpl(String loggableDreamPrefix) {
-        mLoggableDreamPrefix = loggableDreamPrefix;
+    DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+        mLoggableDreamPrefixes = loggableDreamPrefixes;
     }
 
     @Override
@@ -38,13 +38,20 @@
         if (eventID <= 0) {
             return;
         }
-        final boolean isFirstPartyDream =
-                mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
-                        mLoggableDreamPrefix);
         FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
                 /* uid = 1 */ 0,
                 /* event_id = 2 */ eventID,
                 /* instance_id = 3 */ 0,
-                /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+                /* dream_component_name = 4 */
+                isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+    }
+
+    private boolean isFirstPartyDream(String dreamComponentName) {
+        for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+            if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 2817d1b..28dc318 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -26,6 +26,7 @@
 import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontUpdateRequest;
 import android.graphics.fonts.SystemFonts;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.ResultReceiver;
 import android.os.SharedMemory;
@@ -35,8 +36,10 @@
 import android.util.AndroidException;
 import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.graphics.fonts.IFontManager;
 import com.android.internal.security.VerityUtils;
@@ -47,7 +50,9 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
 import java.nio.DirectByteBuffer;
@@ -155,9 +160,30 @@
     }
 
     private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
+
+        private final String[] mDerCertPaths;
+
+        FsverityUtilImpl(String[] derCertPaths) {
+            mDerCertPaths = derCertPaths;
+        }
+
         @Override
-        public boolean hasFsverity(String filePath) {
-            return VerityUtils.hasFsverity(filePath);
+        public boolean isFromTrustedProvider(String fontPath, byte[] pkcs7Signature) {
+            final byte[] digest = VerityUtils.getFsverityDigest(fontPath);
+            if (digest == null) {
+                Log.w(TAG, "Failed to get fs-verity digest for " + fontPath);
+                return false;
+            }
+            for (String certPath : mDerCertPaths) {
+                try (InputStream is = new FileInputStream(certPath)) {
+                    if (VerityUtils.verifyPkcs7DetachedSignature(pkcs7Signature, digest, is)) {
+                        return true;
+                    }
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to read certificate file: " + certPath);
+                }
+            }
+            return false;
         }
 
         @Override
@@ -175,11 +201,15 @@
     @NonNull
     private final Context mContext;
 
+    private final boolean mIsSafeMode;
+
     private final Object mUpdatableFontDirLock = new Object();
 
+    private String mDebugCertFilePath = null;
+
     @GuardedBy("mUpdatableFontDirLock")
     @Nullable
-    private final UpdatableFontDir mUpdatableFontDir;
+    private UpdatableFontDir mUpdatableFontDir;
 
     // mSerializedFontMapLock can be acquired while holding mUpdatableFontDirLock.
     // mUpdatableFontDirLock should not be newly acquired while holding mSerializedFontMapLock.
@@ -195,22 +225,43 @@
             UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
         }
         mContext = context;
-        mUpdatableFontDir = createUpdatableFontDir(safeMode);
+        mIsSafeMode = safeMode;
         initialize();
     }
 
     @Nullable
-    private static UpdatableFontDir createUpdatableFontDir(boolean safeMode) {
+    private UpdatableFontDir createUpdatableFontDir() {
         // Never read updatable font files in safe mode.
-        if (safeMode) return null;
+        if (mIsSafeMode) return null;
         // If apk verity is supported, fs-verity should be available.
         if (!VerityUtils.isFsVeritySupported()) return null;
+
+        String[] certs = mContext.getResources().getStringArray(
+                R.array.config_fontManagerServiceCerts);
+
+        if (mDebugCertFilePath != null && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+            String[] tmp = new String[certs.length + 1];
+            System.arraycopy(certs, 0, tmp, 0, certs.length);
+            tmp[certs.length] = mDebugCertFilePath;
+            certs = tmp;
+        }
+
         return new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser(),
-                new FsverityUtilImpl(), new File(CONFIG_XML_FILE));
+                new FsverityUtilImpl(certs), new File(CONFIG_XML_FILE));
+    }
+
+    /**
+     * Add debug certificate to the cert list. This must be called only on userdebug/eng
+     * build.
+     * @param debugCertPath a debug certificate file path
+     */
+    public void addDebugCertificate(@Nullable String debugCertPath) {
+        mDebugCertFilePath = debugCertPath;
     }
 
     private void initialize() {
         synchronized (mUpdatableFontDirLock) {
+            mUpdatableFontDir = createUpdatableFontDir();
             if (mUpdatableFontDir == null) {
                 setSerializedFontMap(serializeSystemServerFontMap());
                 return;
@@ -233,12 +284,12 @@
 
     /* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
             throws SystemFontException {
-        if (mUpdatableFontDir == null) {
-            throw new SystemFontException(
-                    FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
-                    "The font updater is disabled.");
-        }
         synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
+                        "The font updater is disabled.");
+            }
             // baseVersion == -1 only happens from shell command. This is filtered and treated as
             // error from SystemApi call.
             if (baseVersion != -1 && mUpdatableFontDir.getConfigVersion() != baseVersion) {
@@ -273,10 +324,10 @@
     }
 
     /* package */ Map<String, File> getFontFileMap() {
-        if (mUpdatableFontDir == null) {
-            return Collections.emptyMap();
-        }
         synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                return Collections.emptyMap();
+            }
             return mUpdatableFontDir.getPostScriptMap();
         }
     }
@@ -302,10 +353,10 @@
      * Returns an active system font configuration.
      */
     public @NonNull FontConfig getSystemFontConfig() {
-        if (mUpdatableFontDir == null) {
-            return SystemFonts.getSystemPreinstalledFontConfig();
-        }
         synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                return SystemFonts.getSystemPreinstalledFontConfig();
+            }
             return mUpdatableFontDir.getSystemFontConfig();
         }
     }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 88145bd..4cd0d6e 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -28,6 +28,7 @@
 import android.graphics.fonts.FontVariationAxis;
 import android.graphics.fonts.SystemFonts;
 import android.os.Binder;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.ShellCommand;
@@ -103,6 +104,10 @@
         w.println("update-family [family definition XML path]");
         w.println("    Update font families with the new definitions.");
         w.println();
+        w.println("install-debug-cert [cert file path]");
+        w.println("    Install debug certificate file. This command can be used only on userdebug");
+        w.println("    or eng device with root user.");
+        w.println();
         w.println("clear");
         w.println("    Remove all installed font files and reset to the initial state.");
         w.println();
@@ -322,6 +327,33 @@
         return 0;
     }
 
+    private int installCert(ShellCommand shell) throws SystemFontException {
+        if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
+            throw new SecurityException("Only userdebug/eng device can add debug certificate");
+        }
+        if (Binder.getCallingUid() != Process.ROOT_UID) {
+            throw new SecurityException("Only root can add debug certificate");
+        }
+
+        String certPath = shell.getNextArg();
+        if (certPath == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_DEBUG_CERTIFICATE,
+                    "Cert file path argument is required.");
+        }
+        File file = new File(certPath);
+        if (!file.isFile()) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_DEBUG_CERTIFICATE,
+                    "Cert file (" + file + ") is not found");
+        }
+
+        mService.addDebugCertificate(certPath);
+        mService.restart();
+        shell.getOutPrintWriter().println("Success");
+        return 0;
+    }
+
     private int update(ShellCommand shell) throws SystemFontException {
         String fontPath = shell.getNextArg();
         if (fontPath == null) {
@@ -494,6 +526,8 @@
                     return restart(shell);
                 case "status":
                     return status(shell);
+                case "install-debug-cert":
+                    return installCert(shell);
                 default:
                     return shell.handleDefaultCommands(cmd);
             }
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 743b4d9..457d5b7 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -40,6 +40,8 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -59,6 +61,8 @@
     private static final String TAG = "UpdatableFontDir";
     private static final String RANDOM_DIR_PREFIX = "~~";
 
+    private static final String FONT_SIGNATURE_FILE = "font.fsv_sig";
+
     /** Interface to mock font file access in tests. */
     interface FontFileParser {
         String getPostScriptName(File file) throws IOException;
@@ -72,7 +76,7 @@
 
     /** Interface to mock fs-verity in tests. */
     interface FsverityUtil {
-        boolean hasFsverity(String path);
+        boolean isFromTrustedProvider(String path, byte[] pkcs7Signature);
 
         void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
 
@@ -188,12 +192,35 @@
                     FileUtils.deleteContentsAndDir(dir);
                     continue;
                 }
+
+                File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
+                if (!signatureFile.exists()) {
+                    Slog.i(TAG, "The signature file is missing.");
+                    FileUtils.deleteContentsAndDir(dir);
+                    continue;
+                }
+                byte[] signature;
+                try {
+                    signature = Files.readAllBytes(Paths.get(signatureFile.getAbsolutePath()));
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to read signature file.");
+                    return;
+                }
+
                 File[] files = dir.listFiles();
-                if (files == null || files.length != 1) {
+                if (files == null || files.length != 2) {
                     Slog.e(TAG, "Unexpected files in dir: " + dir);
                     return;
                 }
-                FontFileInfo fontFileInfo = validateFontFile(files[0]);
+
+                File fontFile;
+                if (files[0].equals(signatureFile)) {
+                    fontFile = files[1];
+                } else {
+                    fontFile = files[0];
+                }
+
+                FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
                 if (fontConfig == null) {
                     fontConfig = getSystemFontConfig();
                 }
@@ -359,9 +386,25 @@
             } catch (ErrnoException e) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
-                        "Failed to change mode to 711", e);
+                        "Failed to change font file mode to 644", e);
             }
-            FontFileInfo fontFileInfo = validateFontFile(newFontFile);
+            File signatureFile = new File(newDir, FONT_SIGNATURE_FILE);
+            try (FileOutputStream out = new FileOutputStream(signatureFile)) {
+                out.write(pkcs7Signature);
+            } catch (IOException e) {
+                // TODO: Do we need new error code for signature write failure?
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to write font signature file to storage.", e);
+            }
+            try {
+                Os.chmod(signatureFile.getAbsolutePath(), 0600);
+            } catch (ErrnoException e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to change the signature file mode to 600", e);
+            }
+            FontFileInfo fontFileInfo = validateFontFile(newFontFile, pkcs7Signature);
 
             // Try to create Typeface and treat as failure something goes wrong.
             try {
@@ -478,8 +521,9 @@
      * is higher than the currently used font.
      */
     @NonNull
-    private FontFileInfo validateFontFile(File file) throws SystemFontException {
-        if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
+    private FontFileInfo validateFontFile(File file, byte[] pkcs7Signature)
+            throws SystemFontException {
+        if (!mFsverityUtil.isFromTrustedProvider(file.getAbsolutePath(), pkcs7Signature)) {
             throw new SystemFontException(
                     FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
                     "Font validation failed. Fs-verity is not enabled: " + file);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 565b2ac..4d1c5ae 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3204,7 +3204,7 @@
     void setInputMethodLocked(String id, int subtypeId) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
-            throw new IllegalArgumentException("Unknown id: " + id);
+            throw getExceptionForUnknownImeId(id);
         }
 
         // See if we need to notify a subtype change within the same IME.
@@ -3925,8 +3925,7 @@
 
     @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
-    public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
-            int displayId) {
+    public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         // Always call subtype picker, because subtype picker is a superset of input method
         // picker.
         super.showInputMethodPickerFromSystem_enforcePermission();
@@ -3947,12 +3946,25 @@
         }
     }
 
+    @NonNull
+    private static IllegalArgumentException getExceptionForUnknownImeId(
+            @Nullable String imeId) {
+        return new IllegalArgumentException("Unknown id: " + imeId);
+    }
+
     @BinderThread
     private void setInputMethod(@NonNull IBinder token, String id) {
+        final int callingUid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(callingUid);
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
+            final InputMethodInfo imi = mMethodMap.get(id);
+            if (imi == null || !canCallerAccessInputMethod(
+                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                throw getExceptionForUnknownImeId(id);
+            }
             setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
         }
     }
@@ -3960,14 +3972,20 @@
     @BinderThread
     private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
             InputMethodSubtype subtype) {
+        final int callingUid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(callingUid);
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
+            final InputMethodInfo imi = mMethodMap.get(id);
+            if (imi == null || !canCallerAccessInputMethod(
+                    imi.getPackageName(), callingUid, userId, mSettings)) {
+                throw getExceptionForUnknownImeId(id);
+            }
             if (subtype != null) {
                 setInputMethodWithSubtypeIdLocked(token, id,
-                        SubtypeUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
-                                subtype.hashCode()));
+                        SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()));
             } else {
                 setInputMethod(token, id);
             }
diff --git a/services/core/java/com/android/server/locales/AppUpdateTracker.java b/services/core/java/com/android/server/locales/AppUpdateTracker.java
new file mode 100644
index 0000000..3474f1e
--- /dev/null
+++ b/services/core/java/com/android/server/locales/AppUpdateTracker.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locales;
+
+import android.app.LocaleConfig;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Track when a app is being updated.
+ */
+public class AppUpdateTracker {
+    private static final String TAG = "AppUpdateTracker";
+
+    private final Context mContext;
+    private final LocaleManagerService mLocaleManagerService;
+    private final LocaleManagerBackupHelper mBackupHelper;
+
+    AppUpdateTracker(Context context, LocaleManagerService localeManagerService,
+            LocaleManagerBackupHelper backupHelper) {
+        mContext = context;
+        mLocaleManagerService = localeManagerService;
+        mBackupHelper = backupHelper;
+    }
+
+    /**
+     * <p><b>Note:</b> This is invoked by service's common monitor
+     * {@link LocaleManagerServicePackageMonitor#onPackageUpdateFinished} when a package is upgraded
+     * on device.
+     */
+    public void onPackageUpdateFinished(String packageName, int uid) {
+        Log.d(TAG, "onPackageUpdateFinished " + packageName);
+        int userId = UserHandle.getUserId(uid);
+        cleanApplicationLocalesIfNeeded(packageName, userId);
+    }
+
+    /**
+     * When the user has set per-app locales for a specific application from a delegate selector,
+     * and then the LocaleConfig of that application is removed in the upgraded version, the per-app
+     * locales needs to be reset to system default locales to avoid the user being unable to change
+     * system locales setting.
+     */
+    private void cleanApplicationLocalesIfNeeded(String packageName, int userId) {
+        Set<String> packageNames = new ArraySet<>();
+        SharedPreferences delegateAppLocalePackages = mBackupHelper.getPersistedInfo();
+        if (delegateAppLocalePackages != null) {
+            packageNames = delegateAppLocalePackages.getStringSet(Integer.toString(userId),
+                    new ArraySet<>());
+        }
+
+        try {
+            LocaleList appLocales = mLocaleManagerService.getApplicationLocales(packageName,
+                    userId);
+            if (appLocales.isEmpty() || isLocalesExistedInLocaleConfig(appLocales, packageName,
+                    userId) || !packageNames.contains(packageName)) {
+                return;
+            }
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Exception when getting locales for " + packageName, e);
+            return;
+        }
+
+        Slog.d(TAG, "Clear app locales for " + packageName);
+        try {
+            mLocaleManagerService.setApplicationLocales(packageName, userId,
+                    LocaleList.forLanguageTags(""), false);
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Could not clear locales for " + packageName, e);
+        }
+    }
+
+    /**
+     * Check whether the LocaleConfig is existed and the per-app locales is presented in the
+     * LocaleConfig file after the application is upgraded.
+     */
+    private boolean isLocalesExistedInLocaleConfig(LocaleList appLocales, String packageName,
+            int userId) {
+        LocaleList packageLocalesList = getPackageLocales(packageName, userId);
+        HashSet<Locale> packageLocales = new HashSet<>();
+
+        if (isSettingsAppLocalesOptIn()) {
+            if (packageLocalesList == null || packageLocalesList.isEmpty()) {
+                // The app locale feature is not enabled by the app
+                Slog.d(TAG, "opt-in: the app locale feature is not enabled");
+                return false;
+            }
+        } else {
+            if (packageLocalesList != null && packageLocalesList.isEmpty()) {
+                // The app locale feature is not enabled by the app
+                Slog.d(TAG, "opt-out: the app locale feature is not enabled");
+                return false;
+            }
+        }
+
+        if (packageLocalesList != null && !packageLocalesList.isEmpty()) {
+            // The app has added the supported locales into the LocaleConfig
+            for (int i = 0; i < packageLocalesList.size(); i++) {
+                packageLocales.add(packageLocalesList.get(i));
+            }
+            if (!matchesLocale(packageLocales, appLocales)) {
+                // The set app locales do not match with the list of app supported locales
+                Slog.d(TAG, "App locales: " + appLocales.toLanguageTags()
+                        + " are not existed in the supported locale list");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Get locales from LocaleConfig.
+     */
+    @VisibleForTesting
+    public LocaleList getPackageLocales(String packageName, int userId) {
+        try {
+            LocaleConfig localeConfig = new LocaleConfig(
+                    mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)));
+            if (localeConfig.getStatus() == LocaleConfig.STATUS_SUCCESS) {
+                return localeConfig.getSupportedLocales();
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Can not found the package name : " + packageName + " / " + e);
+        }
+        return null;
+    }
+
+    /**
+     * Check whether the feature to show per-app locales list in Settings is enabled.
+     */
+    @VisibleForTesting
+    public boolean isSettingsAppLocalesOptIn() {
+        return FeatureFlagUtils.isEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
+    }
+
+    private boolean matchesLocale(HashSet<Locale> supported, LocaleList appLocales) {
+        if (supported.size() <= 0 || appLocales.size() <= 0) {
+            return true;
+        }
+
+        for (int i = 0; i < appLocales.size(); i++) {
+            final Locale appLocale = appLocales.get(i);
+            if (supported.stream().anyMatch(
+                    locale -> LocaleList.matchesLanguageAndScript(locale, appLocale))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 67c931f..898c6f1 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -27,14 +27,17 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Environment;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -44,18 +47,20 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
 import java.time.Clock;
 import java.time.Duration;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Set;
 
 /**
  * Helper class for managing backup and restore of app-specific locales.
@@ -68,9 +73,14 @@
     private static final String PACKAGE_XML_TAG = "package";
     private static final String ATTR_PACKAGE_NAME = "name";
     private static final String ATTR_LOCALES = "locales";
-    private static final String ATTR_CREATION_TIME_MILLIS = "creationTimeMillis";
+    private static final String ATTR_DELEGATE_SELECTOR = "delegate_selector";
 
     private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";
+    /**
+     * The name of the xml file used to persist the target package name that sets per-app locales
+     * from the delegate selector.
+     */
+    private static final String LOCALES_FROM_DELEGATE_PREFS = "LocalesFromDelegatePrefs.xml";
     // Stage data would be deleted on reboot since it's stored in memory. So it's retained until
     // retention period OR next reboot, whichever happens earlier.
     private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
@@ -85,23 +95,28 @@
     // SparseArray because it is more memory-efficient than a HashMap.
     private final SparseArray<StagedData> mStagedData;
 
+    // SharedPreferences to store packages whose app-locale was set by a delegate, as opposed to
+    // the application setting the app-locale itself.
+    private final SharedPreferences mDelegateAppLocalePackages;
     private final BroadcastReceiver mUserMonitor;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
             PackageManager packageManager, HandlerThread broadcastHandlerThread) {
         this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
-                new SparseArray<>(), broadcastHandlerThread);
+                new SparseArray<>(), broadcastHandlerThread, null);
     }
 
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
             PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
-            HandlerThread broadcastHandlerThread) {
+            HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
         mPackageManager = packageManager;
         mClock = clock;
         mStagedData = stagedData;
+        mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
+                : createPersistedInfo();
 
         mUserMonitor = new UserMonitor();
         IntentFilter filter = new IntentFilter();
@@ -127,20 +142,29 @@
             cleanStagedDataForOldEntriesLocked();
         }
 
-        HashMap<String, String> pkgStates = new HashMap<>();
+        HashMap<String, LocalesInfo> pkgStates = new HashMap<>();
         for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
                 PackageManager.ApplicationInfoFlags.of(0), userId)) {
             try {
                 LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
                         appInfo.packageName,
                         userId);
-                // Backup locales only for apps which do have app-specific overrides.
+                // Backup locales and package names for per-app locales set from a delegate
+                // selector only for apps which do have app-specific overrides.
                 if (!appLocales.isEmpty()) {
                     if (DEBUG) {
                         Slog.d(TAG, "Add package=" + appInfo.packageName + " locales="
                                 + appLocales.toLanguageTags() + " to backup payload");
                     }
-                    pkgStates.put(appInfo.packageName, appLocales.toLanguageTags());
+                    boolean localeSetFromDelegate = false;
+                    if (mDelegateAppLocalePackages != null) {
+                        localeSetFromDelegate = mDelegateAppLocalePackages.getStringSet(
+                                Integer.toString(userId), Collections.<String>emptySet()).contains(
+                                appInfo.packageName);
+                    }
+                    LocalesInfo localesInfo = new LocalesInfo(appLocales.toLanguageTags(),
+                            localeSetFromDelegate);
+                    pkgStates.put(appInfo.packageName, localesInfo);
                 }
             } catch (RemoteException | IllegalArgumentException e) {
                 Slog.e(TAG, "Exception when getting locales for package: " + appInfo.packageName,
@@ -200,7 +224,7 @@
 
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload);
 
-        HashMap<String, String> pkgStates;
+        HashMap<String, LocalesInfo> pkgStates;
         try {
             // Parse the input blob into a list of BackupPackageState.
             final TypedXmlPullParser parser = Xml.newFastPullParser();
@@ -222,16 +246,17 @@
             StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>());
 
             for (String pkgName : pkgStates.keySet()) {
-                String languageTags = pkgStates.get(pkgName);
+                LocalesInfo localesInfo = pkgStates.get(pkgName);
                 // Check if the application is already installed for the concerned user.
                 if (isPackageInstalledForUser(pkgName, userId)) {
                     // Don't apply the restore if the locales have already been set for the app.
-                    checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+                    checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
                 } else {
                     // Stage the data if the app isn't installed.
-                    stagedData.mPackageStates.put(pkgName, languageTags);
+                    stagedData.mPackageStates.put(pkgName, localesInfo);
                     if (DEBUG) {
-                        Slog.d(TAG, "Add locales=" + languageTags
+                        Slog.d(TAG, "Add locales=" + localesInfo.mLocales
+                                + " fromDelegate=" + localesInfo.mSetFromDelegate
                                 + " package=" + pkgName + " for lazy restore.");
                     }
                 }
@@ -276,9 +301,11 @@
      * {@link LocaleManagerServicePackageMonitor#onPackageDataCleared} when a package's data
      * is cleared.
      */
-    void onPackageDataCleared() {
+    void onPackageDataCleared(String packageName, int uid) {
         try {
             notifyBackupManager();
+            int userId = UserHandle.getUserId(uid);
+            removePackageFromPersistedInfo(packageName, userId);
         } catch (Exception e) {
             Slog.e(TAG, "Exception in onPackageDataCleared.", e);
         }
@@ -289,9 +316,11 @@
      * {@link LocaleManagerServicePackageMonitor#onPackageRemoved} when a package is removed
      * from device.
      */
-    void onPackageRemoved() {
+    void onPackageRemoved(String packageName, int uid) {
         try {
             notifyBackupManager();
+            int userId = UserHandle.getUserId(uid);
+            removePackageFromPersistedInfo(packageName, userId);
         } catch (Exception e) {
             Slog.e(TAG, "Exception in onPackageRemoved.", e);
         }
@@ -317,7 +346,12 @@
      * case, we want to keep the user settings and discard the restore.
      */
     private void checkExistingLocalesAndApplyRestore(@NonNull String pkgName,
-            @NonNull String languageTags, int userId) {
+            LocalesInfo localesInfo, int userId) {
+        if (localesInfo == null) {
+            Slog.w(TAG, "No locales info for " + pkgName);
+            return;
+        }
+
         try {
             LocaleList currLocales = mLocaleManagerService.getApplicationLocales(
                     pkgName,
@@ -325,16 +359,17 @@
             if (!currLocales.isEmpty()) {
                 return;
             }
-        } catch (RemoteException e) {
+        } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Could not check for current locales before restoring", e);
         }
 
         // Restore the locale immediately
         try {
             mLocaleManagerService.setApplicationLocales(pkgName, userId,
-                    LocaleList.forLanguageTags(languageTags));
+                    LocaleList.forLanguageTags(localesInfo.mLocales), localesInfo.mSetFromDelegate);
             if (DEBUG) {
-                Slog.d(TAG, "Restored locales=" + languageTags + " for package=" + pkgName);
+                Slog.d(TAG, "Restored locales=" + localesInfo.mLocales + " fromDelegate="
+                        + localesInfo.mSetFromDelegate + " for package=" + pkgName);
             }
         } catch (RemoteException | IllegalArgumentException e) {
             Slog.e(TAG, "Could not restore locales for " + pkgName, e);
@@ -348,18 +383,21 @@
     /**
      * Parses the backup data from the serialized xml input stream.
      */
-    private @NonNull HashMap<String, String> readFromXml(XmlPullParser parser)
+    private @NonNull HashMap<String, LocalesInfo> readFromXml(TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
-        HashMap<String, String> packageStates = new HashMap<>();
+        HashMap<String, LocalesInfo> packageStates = new HashMap<>();
         int depth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, depth)) {
             if (parser.getName().equals(PACKAGE_XML_TAG)) {
                 String packageName = parser.getAttributeValue(/* namespace= */ null,
                         ATTR_PACKAGE_NAME);
                 String languageTags = parser.getAttributeValue(/* namespace= */ null, ATTR_LOCALES);
+                boolean delegateSelector = parser.getAttributeBoolean(/* namespace= */ null,
+                        ATTR_DELEGATE_SELECTOR);
 
                 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(languageTags)) {
-                    packageStates.put(packageName, languageTags);
+                    LocalesInfo localesInfo = new LocalesInfo(languageTags, delegateSelector);
+                    packageStates.put(packageName, localesInfo);
                 }
             }
         }
@@ -369,8 +407,8 @@
     /**
      * Converts the list of app backup data into a serialized xml stream.
      */
-    private static void writeToXml(OutputStream stream, @NonNull HashMap<String, String> pkgStates)
-            throws IOException {
+    private static void writeToXml(OutputStream stream,
+            @NonNull HashMap<String, LocalesInfo> pkgStates) throws IOException {
         if (pkgStates.isEmpty()) {
             // No need to write anything at all if pkgStates is empty.
             return;
@@ -384,7 +422,9 @@
         for (String pkg : pkgStates.keySet()) {
             out.startTag(/* namespace= */ null, PACKAGE_XML_TAG);
             out.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, pkg);
-            out.attribute(/* namespace= */ null, ATTR_LOCALES, pkgStates.get(pkg));
+            out.attribute(/* namespace= */ null, ATTR_LOCALES, pkgStates.get(pkg).mLocales);
+            out.attributeBoolean(/* namespace= */ null, ATTR_DELEGATE_SELECTOR,
+                    pkgStates.get(pkg).mSetFromDelegate);
             out.endTag(/*namespace= */ null, PACKAGE_XML_TAG);
         }
 
@@ -394,14 +434,24 @@
 
     static class StagedData {
         final long mCreationTimeMillis;
-        final HashMap<String, String> mPackageStates;
+        final HashMap<String, LocalesInfo> mPackageStates;
 
-        StagedData(long creationTimeMillis, HashMap<String, String> pkgStates) {
+        StagedData(long creationTimeMillis, HashMap<String, LocalesInfo> pkgStates) {
             mCreationTimeMillis = creationTimeMillis;
             mPackageStates = pkgStates;
         }
     }
 
+    static class LocalesInfo {
+        final String mLocales;
+        final boolean mSetFromDelegate;
+
+        LocalesInfo(String locales, boolean setFromDelegate) {
+            mLocales = locales;
+            mSetFromDelegate = setFromDelegate;
+        }
+    }
+
     /**
      * Broadcast listener to capture user removed event.
      *
@@ -416,6 +466,7 @@
                     final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                     synchronized (mStagedDataLock) {
                         deleteStagedDataLocked(userId);
+                        removeProfileFromPersistedInfo(userId);
                     }
                 }
             } catch (Exception e) {
@@ -443,11 +494,11 @@
 
         StagedData stagedData = mStagedData.get(userId);
         for (String pkgName : stagedData.mPackageStates.keySet()) {
-            String languageTags = stagedData.mPackageStates.get(pkgName);
+            LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName);
 
             if (pkgName.equals(packageName)) {
 
-                checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+                checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
 
                 // Remove the restored entry from the staged data list.
                 stagedData.mPackageStates.remove(pkgName);
@@ -463,4 +514,98 @@
             }
         }
     }
+
+    SharedPreferences createPersistedInfo() {
+        final File prefsFile = new File(
+                Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
+                LOCALES_FROM_DELEGATE_PREFS);
+        return mContext.createDeviceProtectedStorageContext().getSharedPreferences(prefsFile,
+                Context.MODE_PRIVATE);
+    }
+
+    public SharedPreferences getPersistedInfo() {
+        return mDelegateAppLocalePackages;
+    }
+
+    private void removePackageFromPersistedInfo(String packageName, @UserIdInt int userId) {
+        if (mDelegateAppLocalePackages == null) {
+            Slog.w(TAG, "Failed to persist data into the shared preference!");
+            return;
+        }
+
+        String key = Integer.toString(userId);
+        Set<String> packageNames = new ArraySet<>(
+                mDelegateAppLocalePackages.getStringSet(key, new ArraySet<>()));
+        if (packageNames.contains(packageName)) {
+            if (DEBUG) {
+                Slog.d(TAG, "remove " + packageName + " from persisted info");
+            }
+            packageNames.remove(packageName);
+            SharedPreferences.Editor editor = mDelegateAppLocalePackages.edit();
+            editor.putStringSet(key, packageNames);
+
+            // commit and log the result.
+            if (!editor.commit()) {
+                Slog.e(TAG, "Failed to commit data!");
+            }
+        }
+    }
+
+    private void removeProfileFromPersistedInfo(@UserIdInt int userId) {
+        String key = Integer.toString(userId);
+
+        if (mDelegateAppLocalePackages == null || !mDelegateAppLocalePackages.contains(key)) {
+            Slog.w(TAG, "The profile is not existed in the persisted info");
+            return;
+        }
+
+        if (!mDelegateAppLocalePackages.edit().remove(key).commit()) {
+            Slog.e(TAG, "Failed to commit data!");
+        }
+    }
+
+    /**
+     * Persists the package name of per-app locales set from a delegate selector.
+     *
+     * <p>This information is used when the user has set per-app locales for a specific application
+     * from the delegate selector, and then the LocaleConfig of that application is removed in the
+     * upgraded version, the per-app locales needs to be reset to system default locales to avoid
+     * the user being unable to change system locales setting.
+     */
+    void persistLocalesModificationInfo(@UserIdInt int userId, String packageName,
+            boolean fromDelegate, boolean emptyLocales) {
+        if (mDelegateAppLocalePackages == null) {
+            Slog.w(TAG, "Failed to persist data into the shared preference!");
+            return;
+        }
+
+        SharedPreferences.Editor editor = mDelegateAppLocalePackages.edit();
+        String user = Integer.toString(userId);
+        Set<String> packageNames = new ArraySet<>(
+                mDelegateAppLocalePackages.getStringSet(user, new ArraySet<>()));
+        if (fromDelegate && !emptyLocales) {
+            if (!packageNames.contains(packageName)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "persist package: " + packageName);
+                }
+                packageNames.add(packageName);
+                editor.putStringSet(user, packageNames);
+            }
+        } else {
+            // Remove the package name if per-app locales was not set from the delegate selector
+            // or they were set to empty.
+            if (packageNames.contains(packageName)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "remove package: " + packageName);
+                }
+                packageNames.remove(packageName);
+                editor.putStringSet(user, packageNames);
+            }
+        }
+
+        // commit and log the result.
+        if (!editor.commit()) {
+            Slog.e(TAG, "failed to commit locale setter info");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 4ce0320..39b9f1f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -94,9 +94,11 @@
 
         mBackupHelper = new LocaleManagerBackupHelper(this,
                 mPackageManager, broadcastHandlerThread);
+        AppUpdateTracker appUpdateTracker =
+                new AppUpdateTracker(mContext, this, mBackupHelper);
 
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
-                systemAppUpdateTracker);
+                systemAppUpdateTracker, appUpdateTracker);
         mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
                 UserHandle.ALL,
                 true);
@@ -147,8 +149,9 @@
     private final class LocaleManagerBinderService extends ILocaleManager.Stub {
         @Override
         public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
-                @NonNull LocaleList locales) throws RemoteException {
-            LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales);
+                @NonNull LocaleList locales, boolean fromDelegate) throws RemoteException {
+            LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales,
+                    fromDelegate);
         }
 
         @Override
@@ -178,7 +181,8 @@
      * Sets the current UI locales for a specified app.
      */
     public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
-            @NonNull LocaleList locales) throws RemoteException, IllegalArgumentException {
+            @NonNull LocaleList locales, boolean fromDelegate)
+            throws RemoteException, IllegalArgumentException {
         AppLocaleChangedAtomRecord atomRecordForMetrics = new
                 AppLocaleChangedAtomRecord(Binder.getCallingUid());
         try {
@@ -203,6 +207,8 @@
                 enforceChangeConfigurationPermission(atomRecordForMetrics);
             }
 
+            mBackupHelper.persistLocalesModificationInfo(userId, appPackageName, fromDelegate,
+                    locales.isEmpty());
             final long token = Binder.clearCallingIdentity();
             try {
                 setApplicationLocalesUnchecked(appPackageName, userId, locales,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index 32080ef..1a38f0c 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -34,11 +34,13 @@
 final class LocaleManagerServicePackageMonitor extends PackageMonitor {
     private LocaleManagerBackupHelper mBackupHelper;
     private SystemAppUpdateTracker mSystemAppUpdateTracker;
+    private AppUpdateTracker mAppUpdateTracker;
 
     LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper,
-            SystemAppUpdateTracker systemAppUpdateTracker) {
+            SystemAppUpdateTracker systemAppUpdateTracker, AppUpdateTracker appUpdateTracker) {
         mBackupHelper = localeManagerBackupHelper;
         mSystemAppUpdateTracker = systemAppUpdateTracker;
+        mAppUpdateTracker = appUpdateTracker;
     }
 
     @Override
@@ -48,16 +50,17 @@
 
     @Override
     public void onPackageDataCleared(String packageName, int uid) {
-        mBackupHelper.onPackageDataCleared();
+        mBackupHelper.onPackageDataCleared(packageName, uid);
     }
 
     @Override
     public void onPackageRemoved(String packageName, int uid) {
-        mBackupHelper.onPackageRemoved();
+        mBackupHelper.onPackageRemoved(packageName, uid);
     }
 
     @Override
     public void onPackageUpdateFinished(String packageName, int uid) {
+        mAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
         mSystemAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
     }
 }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
index 803b5a3..c5069e5 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerShellCommand.java
@@ -56,7 +56,8 @@
         pw.println("Locale manager (locale) shell commands:");
         pw.println("  help");
         pw.println("      Print this help text.");
-        pw.println("  set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]");
+        pw.println("  set-app-locales <PACKAGE_NAME> [--user <USER_ID>] [--locales <LOCALE_INFO>]"
+                + "[--delegate <FROM_DELEGATE>]");
         pw.println("      Set the locales for the specified app.");
         pw.println("      --user <USER_ID>: apply for the given user, "
                 + "the current user is used when unspecified.");
@@ -64,6 +65,8 @@
                 + "as a single String separated by commas");
         pw.println("                 Empty locale list is used when unspecified.");
         pw.println("                 eg. en,en-US,hi ");
+        pw.println("      --delegate <FROM_DELEGATE>: The locales are set from a delegate, "
+                + "the value could be true or false. false is the default when unspecified.");
         pw.println("  get-app-locales <PACKAGE_NAME> [--user <USER_ID>]");
         pw.println("      Get the locales for the specified app.");
         pw.println("      --user <USER_ID>: get for the given user, "
@@ -77,6 +80,7 @@
         if (packageName != null) {
             int userId = ActivityManager.getCurrentUser();
             LocaleList locales = LocaleList.getEmptyLocaleList();
+            boolean fromDelegate = false;
             do {
                 String option = getNextOption();
                 if (option == null) {
@@ -91,6 +95,10 @@
                         locales = parseLocales();
                         break;
                     }
+                    case "--delegate": {
+                        fromDelegate = parseFromDelegate();
+                        break;
+                    }
                     default: {
                         throw new IllegalArgumentException("Unknown option: " + option);
                     }
@@ -98,7 +106,7 @@
             } while (true);
 
             try {
-                mBinderService.setApplicationLocales(packageName, userId, locales);
+                mBinderService.setApplicationLocales(packageName, userId, locales, fromDelegate);
             } catch (RemoteException e) {
                 getOutPrintWriter().println("Remote Exception: " + e);
             } catch (IllegalArgumentException e) {
@@ -148,12 +156,26 @@
     }
 
     private LocaleList parseLocales() {
-        if (getRemainingArgsCount() <= 0) {
+        String locales = getNextArg();
+        if (locales == null) {
             return LocaleList.getEmptyLocaleList();
+        } else {
+            if (locales.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown locales: " + locales);
+            }
+            return LocaleList.forLanguageTags(locales);
         }
-        String[] args = peekRemainingArgs();
-        String inputLocales = args[0];
-        LocaleList locales = LocaleList.forLanguageTags(inputLocales);
-        return locales;
+    }
+
+    private boolean parseFromDelegate() {
+        String result = getNextArg();
+        if (result == null) {
+            return false;
+        } else {
+            if (result.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown source: " + result);
+            }
+            return Boolean.parseBoolean(result);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 1fa56bc..2d015a5d 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -25,6 +25,7 @@
 import android.location.GnssMeasurementCorrections;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
+import android.location.GnssSignalType;
 import android.location.GnssStatus;
 import android.location.Location;
 import android.os.Binder;
@@ -1144,6 +1145,13 @@
         onCapabilitiesChanged(oldCapabilities, mCapabilities);
     }
 
+    @NativeEntryPoint
+    void setSignalTypeCapabilities(List<GnssSignalType> signalTypes) {
+        GnssCapabilities oldCapabilities = mCapabilities;
+        mCapabilities = oldCapabilities.withSignalTypes(signalTypes);
+        onCapabilitiesChanged(oldCapabilities, mCapabilities);
+    }
+
     private void onCapabilitiesChanged(GnssCapabilities oldCapabilities,
             GnssCapabilities newCapabilities) {
         Binder.withCleanCallingIdentity(() -> {
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index f144cf8..46f486d 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
@@ -48,11 +49,13 @@
     @NonNull private final Handler mHandler;
     @Nullable private FingerprintManager mFingerprintManager;
     @Nullable private FaceManager mFaceManager;
+    @Nullable private BiometricManager mBiometricManager;
 
     // Entries added by LockSettingsService once a user's synthetic password is known. At this point
     // things are still keyed by userId.
     @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockoutsForFingerprint;
     @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockoutsForFace;
+    @NonNull private final ArrayList<UserAuthInfo> mPendingResetLockouts;
 
     /**
      * Authentication info for a successful user unlock via Synthetic Password. This can be used to
@@ -125,7 +128,6 @@
     }
 
     @Nullable private FaceResetLockoutTask mFaceResetLockoutTask;
-
     private final FaceResetLockoutTask.FinishCallback mFaceFinishCallback = () -> {
         mFaceResetLockoutTask = null;
     };
@@ -135,12 +137,14 @@
         mHandler = handler;
         mPendingResetLockoutsForFingerprint = new ArrayList<>();
         mPendingResetLockoutsForFace = new ArrayList<>();
+        mPendingResetLockouts = new ArrayList<>();
     }
 
     public void systemReady(@Nullable FingerprintManager fingerprintManager,
-            @Nullable FaceManager faceManager) {
+            @Nullable FaceManager faceManager, @Nullable BiometricManager biometricManager) {
         mFingerprintManager = fingerprintManager;
         mFaceManager = faceManager;
+        mBiometricManager = biometricManager;
     }
 
     /**
@@ -151,7 +155,7 @@
      * Note that this should only ever be invoked for successful authentications, otherwise it will
      * consume a Gatekeeper authentication attempt and potentially wipe the user/device.
      *
-     * @param userId The user that the operation will apply for.
+     * @param userId             The user that the operation will apply for.
      * @param gatekeeperPassword The Gatekeeper Password
      */
     void addPendingLockoutResetForUser(int userId, @NonNull byte[] gatekeeperPassword) {
@@ -167,6 +171,12 @@
                 mPendingResetLockoutsForFingerprint.add(new UserAuthInfo(userId,
                         gatekeeperPassword));
             }
+
+            if (mBiometricManager != null) {
+                Slog.d(TAG, "Fingerprint addPendingLockoutResetForUser: " + userId);
+                mPendingResetLockouts.add(new UserAuthInfo(userId,
+                        gatekeeperPassword));
+            }
         });
     }
 
@@ -184,6 +194,14 @@
                         new ArrayList<>(mPendingResetLockoutsForFingerprint));
                 mPendingResetLockoutsForFingerprint.clear();
             }
+
+            if (!mPendingResetLockouts.isEmpty()) {
+                Slog.d(TAG, "Processing pending resetLockouts(Generic)");
+                processPendingLockoutsGeneric(
+                        new ArrayList<>(mPendingResetLockouts));
+                mPendingResetLockouts.clear();
+            }
+
         });
     }
 
@@ -257,6 +275,17 @@
         }
     }
 
+    private void processPendingLockoutsGeneric(List<UserAuthInfo> pendingResetLockouts) {
+        for (UserAuthInfo user : pendingResetLockouts) {
+            Slog.d(TAG, "Resetting biometric lockout for user: " + user.userId);
+            final byte[] hat = requestHatFromGatekeeperPassword(mSpManager, user,
+                    0 /* challenge */);
+            if (hat != null) {
+                mBiometricManager.resetLockout(user.userId, hat);
+            }
+        }
+    }
+
     @Nullable
     private static byte[] requestHatFromGatekeeperPassword(
             @NonNull SyntheticPasswordManager spManager,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c899cf2..92b685c7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -70,6 +70,7 @@
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteDatabase;
 import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.Fingerprint;
@@ -552,6 +553,10 @@
             }
         }
 
+        public BiometricManager getBiometricManager() {
+            return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
+        }
+
         public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
                 int defaultValue) {
             return Settings.Global.getInt(contentResolver, keyName, defaultValue);
@@ -837,7 +842,7 @@
         // TODO: maybe skip this for split system user mode.
         mStorage.prefetchUser(UserHandle.USER_SYSTEM);
         mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
-                mInjector.getFaceManager());
+                mInjector.getFaceManager(), mInjector.getBiometricManager());
     }
 
     private void loadEscrowData() {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index db036b0..d836df5 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -36,6 +36,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -52,6 +53,7 @@
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
@@ -308,48 +310,45 @@
 
     private void writeFile(File path, byte[] data) {
         synchronized (mFileWriteLock) {
-            RandomAccessFile raf = null;
+            // Use AtomicFile to guarantee atomicity of the file write, including when an existing
+            // file is replaced with a new one.  This method is usually used to create new files,
+            // but there are some edge cases in which it is used to replace an existing file.
+            AtomicFile file = new AtomicFile(path);
+            FileOutputStream out = null;
             try {
-                // Write the data to the file, requiring each write to be synchronized to the
-                // underlying storage device immediately to avoid data loss in case of power loss.
-                raf = new RandomAccessFile(path, "rws");
-                // Truncate the file if the data is empty.
-                if (data == null || data.length == 0) {
-                    raf.setLength(0);
-                } else {
-                    raf.write(data, 0, data.length);
-                }
-                raf.close();
-                fsyncDirectory(path.getParentFile());
+                out = file.startWrite();
+                out.write(data);
+                file.finishWrite(out);
+                out = null;
             } catch (IOException e) {
-                Slog.e(TAG, "Error writing to file " + e);
+                Slog.e(TAG, "Error writing file " + path, e);
             } finally {
-                if (raf != null) {
-                    try {
-                        raf.close();
-                    } catch (IOException e) {
-                        Slog.e(TAG, "Error closing file " + e);
-                    }
-                }
+                file.failWrite(out);
             }
+            // For performance reasons, AtomicFile only syncs the file itself, not also the parent
+            // directory.  The latter must be done explicitly here, as some callers need a guarantee
+            // that the file really exists on-disk when this returns.
+            fsyncDirectory(path.getParentFile());
             mCache.putFile(path, data);
         }
     }
 
     private void deleteFile(File path) {
         synchronized (mFileWriteLock) {
+            // Zeroize the file to try to make its contents unrecoverable.  This is *not* guaranteed
+            // to be effective, and in fact it usually isn't, but it doesn't hurt.  We also don't
+            // bother zeroizing |path|.new, which may exist from an interrupted AtomicFile write.
             if (path.exists()) {
-                // Zeroize the file to try to make its contents unrecoverable.  This is *not*
-                // guaranteed to be effective, and in fact it usually isn't, but it doesn't hurt.
                 try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) {
                     final int fileSize = (int) raf.length();
                     raf.write(new byte[fileSize]);
                 } catch (Exception e) {
                     Slog.w(TAG, "Failed to zeroize " + path, e);
                 }
-                path.delete();
-                mCache.putFile(path, null);
             }
+            // To ensure that |path|.new is deleted if it exists, use AtomicFile.delete() here.
+            new AtomicFile(path).delete();
+            mCache.putFile(path, null);
         }
     }
 
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index fdc5bab..497ed03 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -41,7 +42,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ILogAccessDialogCallback;
-import com.android.internal.app.LogAccessDialogActivity;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -62,6 +62,10 @@
 public final class LogcatManagerService extends SystemService {
     private static final String TAG = "LogcatManagerService";
     private static final boolean DEBUG = false;
+    private static final String TARGET_PACKAGE_NAME = "com.android.systemui";
+    private static final String TARGET_ACTIVITY_NAME =
+            "com.android.systemui.logcat.LogAccessDialogActivity";
+    public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
 
     /** How long to wait for the user to approve/decline before declining automatically */
     @VisibleForTesting
@@ -442,6 +446,7 @@
                 mClock.get() + PENDING_CONFIRMATION_TIMEOUT_MILLIS);
         final Intent mIntent = createIntent(client);
         mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mIntent.setComponent(new ComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME));
         mContext.startActivityAsUser(mIntent, UserHandle.SYSTEM);
     }
 
@@ -536,13 +541,13 @@
      * Create the Intent for LogAccessDialogActivity.
      */
     public Intent createIntent(LogAccessClient client) {
-        final Intent intent = new Intent(mContext, LogAccessDialogActivity.class);
+        final Intent intent = new Intent();
 
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 
         intent.putExtra(Intent.EXTRA_PACKAGE_NAME, client.mPackageName);
         intent.putExtra(Intent.EXTRA_UID, client.mUid);
-        intent.putExtra(LogAccessDialogActivity.EXTRA_CALLBACK, mDialogCallback.asBinder());
+        intent.putExtra(EXTRA_CALLBACK, mDialogCallback.asBinder());
 
         return intent;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5dcbb16..c3a5558 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4967,16 +4967,7 @@
             }
             enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
 
-            // If the caller is system, take the package name from the rule's owner rather than
-            // from the caller's package.
-            String rulePkg = pkg;
-            if (isCallingUidSystem()) {
-                if (automaticZenRule.getOwner() != null) {
-                    rulePkg = automaticZenRule.getOwner().getPackageName();
-                }
-            }
-
-            return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
+            return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
                     "addAutomaticZenRule");
         }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4b2c88c..f2c78ad 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -326,7 +326,7 @@
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
             String reason) {
-        if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
+        if (!isSystemRule(automaticZenRule)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
                 component = getActivityInfo(automaticZenRule.getConfigurationActivity());
@@ -582,6 +582,11 @@
         }
     }
 
+    private boolean isSystemRule(AutomaticZenRule rule) {
+        return rule.getOwner() != null
+                && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
+    }
+
     private ServiceInfo getServiceInfo(ComponentName owner) {
         Intent queryIntent = new Intent();
         queryIntent.setComponent(owner);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index bb37e0e..3421eb7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -60,6 +60,7 @@
 import android.content.res.ApkAssets;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FabricatedOverlayInternal;
@@ -881,7 +882,7 @@
                     }
                     Slog.d(TAG, "commit failed: " + e.getMessage(), e);
                     throw new SecurityException("commit failed"
-                            + (DEBUG ? ": " + e.getMessage() : ""));
+                            + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
                 }
             } finally {
                 traceEnd(TRACE_TAG_RRO);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7ea0c04..84b8264 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -427,7 +427,7 @@
             mPm.snapshotComputer().checkPackageFrozen(pkgName);
         }
 
-        final boolean isReplace = request.isReplace();
+        final boolean isReplace = request.isInstallReplace();
         // Also need to kill any apps that are dependent on the library, except the case of
         // installation of new version static shared library.
         if (clientLibPkgs != null) {
@@ -814,6 +814,7 @@
             for (InstallRequest request : requests) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
+                    request.onPrepareStarted();
                     preparePackageLI(request);
                 } catch (PrepareFailure prepareFailure) {
                     request.setError(prepareFailure.error,
@@ -822,6 +823,7 @@
                     request.setOriginPermission(prepareFailure.mConflictingPermission);
                     return;
                 } finally {
+                    request.onPrepareFinished();
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
 
@@ -834,11 +836,13 @@
                 request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
                 final String packageName = packageToScan.getPackageName();
                 try {
+                    request.onScanStarted();
                     final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
                             request.getParseFlags(), request.getScanFlags(),
                             System.currentTimeMillis(), request.getUser(),
                             request.getAbiOverride());
                     request.setScanResult(scanResult);
+                    request.onScanFinished();
                     if (!scannedPackages.add(packageName)) {
                         request.setError(
                                 PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
@@ -966,7 +970,7 @@
         final boolean isRollback =
                 request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
         @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             // moving a complete application; perform an initial scan on the new install location
             scanFlags |= SCAN_INITIAL;
         }
@@ -1349,7 +1353,7 @@
             }
         }
 
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             // We did an in-place move, so dex is ready to roll
             scanFlags |= SCAN_NO_DEX;
             scanFlags |= SCAN_MOVE;
@@ -1668,7 +1672,7 @@
             ParsedPackage parsedPackage) throws PrepareFailure {
         final int status = request.getReturnCode();
         final String statusMsg = request.getReturnMsg();
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
                         request.getMovePackageName(), request.getMoveFromCodePath());
@@ -1888,7 +1892,8 @@
             final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
             final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
 
-            if (installRequest.isReplace()) {
+            installRequest.onCommitStarted();
+            if (installRequest.isInstallReplace()) {
                 AndroidPackage oldPackage = mPm.mPackages.get(packageName);
 
                 // Set the update and install times
@@ -1905,7 +1910,7 @@
                         mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
                                 installRequest.getScannedPackageSetting(),
                                 allUsers, mPm.mSettings.getPackagesLocked());
-                if (installRequest.isSystem()) {
+                if (installRequest.isInstallSystem()) {
                     // Remove existing system package
                     removePackageHelper.removePackage(oldPackage, true);
                     if (!disableSystemPackageLPw(oldPackage)) {
@@ -1973,6 +1978,7 @@
                 mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
                 mPm.updateInstantAppInstallerLocked(packageName);
             }
+            installRequest.onCommitFinished();
         }
         ApplicationPackageManager.invalidateGetPackagesForUidCache();
     }
@@ -2223,7 +2229,7 @@
                         FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                                 | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
             }
-            if (installRequest.isReplace()) {
+            if (installRequest.isInstallReplace()) {
                 mDexManager.notifyPackageUpdated(pkg.getPackageName(),
                         pkg.getBaseApkPath(), pkg.getSplitCodePaths());
             }
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 573082a..4443710 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -18,8 +18,10 @@
 
 import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.os.Process.INVALID_UID;
 
+import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
 import static com.android.server.pm.PackageManagerService.TAG;
 
 import android.annotation.NonNull;
@@ -105,6 +107,12 @@
     @Nullable
     private ScanResult mScanResult;
 
+    private boolean mIsInstallInherit;
+    private boolean mIsInstallForUsers;
+
+    @Nullable
+    private final PackageMetrics mPackageMetrics;
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -116,6 +124,8 @@
                 params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
                 params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
                 params.mDataLoaderType, params.mPackageSource);
+        mPackageMetrics = new PackageMetrics(this);
+        mIsInstallInherit = params.mIsInherit;
     }
 
     // Install existing package as user
@@ -127,6 +137,8 @@
         mPkg = pkg;
         mNewUsers = newUsers;
         mPostInstallRunnable = runnable;
+        mPackageMetrics = new PackageMetrics(this);
+        mIsInstallForUsers = true;
     }
 
     // addForInit
@@ -143,6 +155,7 @@
         mParseFlags = parseFlags;
         mScanFlags = scanFlags;
         mScanResult = scanResult;
+        mPackageMetrics = null; // No real logging from this code path
     }
 
     @Nullable
@@ -200,7 +213,7 @@
         return mInstallArgs == null ? null : mInstallArgs.mObserver;
     }
 
-    public boolean isMoveInstall() {
+    public boolean isInstallMove() {
         return mInstallArgs != null && mInstallArgs.mMoveInfo != null;
     }
 
@@ -278,7 +291,7 @@
         return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null;
     }
 
-    public boolean isInstallForExistingUser() {
+    public boolean isInstallExistingForUser() {
         return mInstallArgs == null;
     }
 
@@ -406,14 +419,22 @@
         return mClearCodeCache;
     }
 
-    public boolean isReplace() {
+    public boolean isInstallReplace() {
         return mReplace;
     }
 
-    public boolean isSystem() {
+    public boolean isInstallSystem() {
         return mSystem;
     }
 
+    public boolean isInstallInherit() {
+        return mIsInstallInherit;
+    }
+
+    public boolean isInstallForUsers() {
+        return mIsInstallForUsers;
+    }
+
     @Nullable
     public PackageSetting getOriginalPackageSetting() {
         return mOriginalPs;
@@ -520,6 +541,10 @@
         return mScanResult.mRequest.mIsPlatformPackage;
     }
 
+    public boolean isInstantInstall() {
+        return (mScanFlags & SCAN_AS_INSTANT_APP) != 0;
+    }
+
     public void assertScanResultExists() {
         if (mScanResult == null) {
             // Should not happen. This indicates a bug in the installation code flow
@@ -529,7 +554,6 @@
                 Slog.e(TAG, "ScanResult is null and it should not happen");
             }
         }
-
     }
 
     public void setScanFlags(int scanFlags) {
@@ -658,4 +682,60 @@
             mRemovedInfo.mRemovedAppId = appId;
         }
     }
+
+    public void onPrepareStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE);
+        }
+    }
+
+    public void onPrepareFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_PREPARE);
+        }
+    }
+
+    public void onScanStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_SCAN);
+        }
+    }
+
+    public void onScanFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_SCAN);
+        }
+    }
+
+    public void onReconcileStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_RECONCILE);
+        }
+    }
+
+    public void onReconcileFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_RECONCILE);
+        }
+    }
+
+    public void onCommitStarted() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepStarted(PackageMetrics.STEP_COMMIT);
+        }
+    }
+
+    public void onCommitFinished() {
+        if (mPackageMetrics != null) {
+            mPackageMetrics.onStepFinished(PackageMetrics.STEP_COMMIT);
+        }
+    }
+
+    public void onInstallCompleted() {
+        if (getReturnCode() == INSTALL_SUCCEEDED) {
+            if (mPackageMetrics != null) {
+                mPackageMetrics.onInstallSucceed();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 88469f5..d13822a 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -93,6 +94,7 @@
     final PackageManagerService mPm;
     final InstallPackageHelper mInstallPackageHelper;
     final RemovePackageHelper mRemovePackageHelper;
+    final boolean mIsInherit;
 
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
             int installFlags, InstallSource installSource, String volumeUuid,
@@ -121,6 +123,7 @@
         mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         mPackageSource = packageSource;
         mPackageLite = packageLite;
+        mIsInherit = false;
     }
 
     InstallingSession(File stagedDir, IPackageInstallObserver2 observer,
@@ -151,6 +154,7 @@
         mRequiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
         mPackageSource = sessionParams.packageSource;
         mPackageLite = packageLite;
+        mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
     }
 
     @Override
@@ -506,6 +510,7 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
+                request.onInstallCompleted();
                 doPostInstall(request);
             }
         }
@@ -531,7 +536,7 @@
     }
 
     private void cleanUpForFailedInstall(InstallRequest request) {
-        if (request.isMoveInstall()) {
+        if (request.isInstallMove()) {
             mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(),
                     request.getMovePackageName(), request.getMoveFromCodePath());
         } else {
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 4e8b0a1..66ef93d 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -95,7 +95,7 @@
 
                 request.closeFreezer();
                 request.runPostInstallRunnable();
-                if (!request.isInstallForExistingUser()) {
+                if (!request.isInstallExistingForUser()) {
                     mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
                 } else if (DEBUG_INSTALL) {
                     // No post-install when we run restore from installExistingPackageForUser
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 72ec510..5d13a45 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -380,6 +380,14 @@
     @GuardedBy("mLock")
     private boolean mStageDirInUse = false;
 
+    /**
+     * True if the installation is already in progress. This is used to prevent the caller
+     * from {@link #commit(IntentSender, boolean) committing} the session again while the
+     * installation is still in progress.
+     */
+    @GuardedBy("mLock")
+    private boolean mInstallationInProgress = false;
+
     /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
     @GuardedBy("mLock")
     private boolean mPermissionsManuallyAccepted = false;
@@ -1692,6 +1700,14 @@
             }
         }
 
+        synchronized (mLock) {
+            if (mInstallationInProgress) {
+                throw new IllegalStateException("Installation is already in progress. Don't "
+                        + "commit session=" + sessionId + " again.");
+            }
+            mInstallationInProgress = true;
+        }
+
         dispatchSessionSealed();
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b302d4a..8089af3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5063,8 +5063,11 @@
                     "getUnsuspendablePackagesForUser");
             final int callingUid = Binder.getCallingUid();
             if (UserHandle.getUserId(callingUid) != userId) {
-                throw new SecurityException("Calling uid " + callingUid
-                        + " cannot query getUnsuspendablePackagesForUser for user " + userId);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "Calling uid " + callingUid
+                                + " cannot query getUnsuspendablePackagesForUser for user "
+                                + userId);
             }
             return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(),
                     packageNames, userId, callingUid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3c1cba3..9f21f11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3707,7 +3707,7 @@
                 fd = ParcelFileDescriptor.dup(getInFileDescriptor());
             }
             if (sizeBytes <= 0) {
-                getErrPrintWriter().println("Error: must specify a APK size");
+                getErrPrintWriter().println("Error: must specify an APK size");
                 return 1;
             }
 
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
new file mode 100644
index 0000000..b725325
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.IntDef;
+import android.os.UserManager;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Metrics class for reporting stats to logging infrastructures like Westworld
+ */
+final class PackageMetrics {
+    public static final int STEP_PREPARE = 1;
+    public static final int STEP_SCAN = 2;
+    public static final int STEP_RECONCILE = 3;
+    public static final int STEP_COMMIT = 4;
+
+    @IntDef(prefix = {"STEP_"}, value = {
+            STEP_PREPARE,
+            STEP_SCAN,
+            STEP_RECONCILE,
+            STEP_COMMIT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StepInt {}
+
+    private final long mInstallStartTimestampMillis;
+    private final SparseArray<InstallStep> mInstallSteps = new SparseArray<>();
+    private final InstallRequest mInstallRequest;
+
+    PackageMetrics(InstallRequest installRequest) {
+        // New instance is used for tracking installation metrics only.
+        // Other metrics should use static methods of this class.
+        mInstallStartTimestampMillis = System.currentTimeMillis();
+        mInstallRequest = installRequest;
+    }
+
+    public void onInstallSucceed() {
+        final long installDurationMillis =
+                System.currentTimeMillis() - mInstallStartTimestampMillis;
+        // write to stats
+        final Pair<int[], long[]> stepDurations = getInstallStepDurations();
+        final int[] newUsers = mInstallRequest.getNewUsers();
+        final int[] originalUsers = mInstallRequest.getOriginUsers();
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+                0 /* session_id */,
+                null /* package_name */,
+                mInstallRequest.getUid() /* uid */,
+                newUsers /* user_ids */,
+                getUserTypes(newUsers) /* user_types */,
+                originalUsers /* original_user_ids */,
+                getUserTypes(originalUsers) /* original_user_types */,
+                mInstallRequest.getReturnCode() /* public_return_code */,
+                0 /* internal_error_code */,
+                0 /* apks_size_bytes */,
+                0 /* version_code */,
+                stepDurations.first /* install_steps */,
+                stepDurations.second /* step_duration_millis */,
+                installDurationMillis /* total_duration_millis */,
+                mInstallRequest.getInstallFlags() /* install_flags */,
+                -1 /* installer_package_uid */,
+                -1 /* original_installer_package_uid */,
+                mInstallRequest.getDataLoaderType() /* data_loader_type */,
+                0 /* user_action_required_type */,
+                mInstallRequest.isInstantInstall() /* is_instant */,
+                mInstallRequest.isInstallReplace() /* is_replace */,
+                mInstallRequest.isInstallSystem() /* is_system */,
+                mInstallRequest.isInstallInherit() /* is_inherit */,
+                mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */,
+                mInstallRequest.isInstallMove() /* is_move_install */,
+                false /* is_staged */
+        );
+    }
+
+    public void onStepStarted(@StepInt int step) {
+        mInstallSteps.put(step, new InstallStep());
+    }
+
+    public void onStepFinished(@StepInt int step) {
+        final InstallStep installStep = mInstallSteps.get(step);
+        if (installStep != null) {
+            // Only valid if the start timestamp is set; otherwise no-op
+            installStep.finish();
+        }
+    }
+
+    // List of steps (e.g., 1, 2, 3) and corresponding list of durations (e.g., 200ms, 100ms, 150ms)
+    private Pair<int[], long[]> getInstallStepDurations() {
+        ArrayList<Integer> steps = new ArrayList<>();
+        ArrayList<Long> durations = new ArrayList<>();
+        for (int i = 0; i < mInstallSteps.size(); i++) {
+            final long duration = mInstallSteps.valueAt(i).getDurationMillis();
+            if (duration >= 0) {
+                steps.add(mInstallSteps.keyAt(i));
+                durations.add(mInstallSteps.valueAt(i).getDurationMillis());
+            }
+        }
+        int[] stepsArray = new int[steps.size()];
+        long[] durationsArray = new long[durations.size()];
+        for (int i = 0; i < stepsArray.length; i++) {
+            stepsArray[i] = steps.get(i);
+            durationsArray[i] = durations.get(i);
+        }
+        return new Pair<>(stepsArray, durationsArray);
+    }
+
+    private static int[] getUserTypes(int[] userIds) {
+        if (userIds == null) {
+            return null;
+        }
+        final int[] userTypes = new int[userIds.length];
+        for (int i = 0; i < userTypes.length; i++) {
+            String userType = UserManagerService.getInstance().getUserInfo(userIds[i]).userType;
+            userTypes[i] = UserManager.getUserTypeForStatsd(userType);
+        }
+        return userTypes;
+    }
+
+    private static class InstallStep {
+        private final long mStartTimestampMillis;
+        private long mDurationMillis = -1;
+        InstallStep() {
+            mStartTimestampMillis = System.currentTimeMillis();
+        }
+        void finish() {
+            mDurationMillis = System.currentTimeMillis() - mStartTimestampMillis;
+        }
+        long getDurationMillis() {
+            return mDurationMillis;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index ffce69e..01d17f6 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -67,6 +67,7 @@
                 new ArrayMap<>();
 
         for (InstallRequest installRequest :  installRequests) {
+            installRequest.onReconcileStarted();
             final String installPackageName = installRequest.getParsedPackage().getPackageName();
 
             // add / replace existing with incoming packages
@@ -90,7 +91,7 @@
 
             final DeletePackageAction deletePackageAction;
             // we only want to try to delete for non system apps
-            if (installRequest.isReplace() && !installRequest.isSystem()) {
+            if (installRequest.isInstallReplace() && !installRequest.isInstallSystem()) {
                 final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
                 final int deleteFlags = PackageManager.DELETE_KEEP_DATA
                         | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
@@ -286,6 +287,10 @@
             }
         }
 
+        for (InstallRequest installRequest : installRequests) {
+            installRequest.onReconcileFinished();
+        }
+
         return result;
     }
 
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index aeb11b7..fbfc84a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -196,8 +196,11 @@
                     appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
                             true /* migrateAppData */);
                 }
-            } catch (IllegalStateException e) {
-                // Device was probably ejected, and we'll process that event momentarily
+            } catch (RuntimeException e) {
+                // The volume was probably already unmounted.  We'll probably process the unmount
+                // event momentarily.  TODO(b/256909937): ignoring errors from prepareUserStorage()
+                // is very dangerous.  Instead, we should fix the race condition that allows this
+                // code to run on an unmounted volume in the first place.
                 Slog.w(TAG, "Failed to prepare storage: " + e);
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d2af78b..f85b11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 
 import android.Manifest;
 import android.accounts.Account;
@@ -314,7 +315,7 @@
     @VisibleForTesting
     static class UserData {
         // Basic user information and properties
-        UserInfo info;
+        @NonNull UserInfo info;
         // Account name used when there is a strong association between a user and an account
         String account;
         // Account information for seeding into a newly created user. This could also be
@@ -3268,11 +3269,39 @@
         }
     }
 
+    /** Checks whether the device is currently in headless system user mode (for any reason). */
+    @Override
+    public boolean isHeadlessSystemUserMode() {
+        synchronized (mUsersLock) {
+            final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM);
+            return !systemUserData.info.isFull();
+        }
+    }
+
     /**
-     * Checks whether the device is really headless system user mode, ignoring system user mode
-     * emulation.
+     * Checks whether the default state of the device is headless system user mode, i.e. what the
+     * mode would be if we did a fresh factory reset.
+     * If the mode is  being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be
+     * returned instead.
+     * Note that, even in the absence of emulation, a device might deviate from the current default
+     * due to an OTA changing the default (which won't change the already-decided mode).
      */
-    private boolean isReallyHeadlessSystemUserMode() {
+    private boolean isDefaultHeadlessSystemUserMode() {
+        if (!Build.isDebuggable()) {
+            return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
+        }
+
+        final String emulatedValue = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
+        if (!TextUtils.isEmpty(emulatedValue)) {
+            if (UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS.equals(emulatedValue)) return true;
+            if (UserManager.SYSTEM_USER_MODE_EMULATION_FULL.equals(emulatedValue)) return false;
+            if (!UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT.equals(emulatedValue)) {
+                Slogf.e(LOG_TAG, "isDefaultHeadlessSystemUserMode(): ignoring invalid valued of "
+                                + "property %s: %s",
+                        SYSTEM_USER_MODE_EMULATION_PROPERTY, emulatedValue);
+            }
+        }
+
         return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
     }
 
@@ -3284,30 +3313,11 @@
         if (!Build.isDebuggable()) {
             return;
         }
-
-        final String emulatedValue = SystemProperties
-                .get(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY);
-        if (TextUtils.isEmpty(emulatedValue)) {
+        if (TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
             return;
         }
 
-        final boolean newHeadlessSystemUserMode;
-        switch (emulatedValue) {
-            case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
-                newHeadlessSystemUserMode = false;
-                break;
-            case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
-                newHeadlessSystemUserMode = true;
-                break;
-            case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
-                newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode();
-                break;
-            default:
-                Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of "
-                        + "property %s: %s", UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY,
-                        emulatedValue);
-                return;
-        }
+        final boolean newHeadlessSystemUserMode = isDefaultHeadlessSystemUserMode();
 
         // Update system user type
         synchronized (mPackagesLock) {
@@ -3343,7 +3353,7 @@
             }
         }
 
-        // Update emulated mode, which will used to triger an update on user packages
+        // Update emulated mode, which will used to trigger an update on user packages
         mUpdatingSystemUserMode = true;
     }
 
@@ -3533,7 +3543,11 @@
             synchronized (mUsersLock) {
                 UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
                 userData.info.flags |= UserInfo.FLAG_SYSTEM;
-                if (!UserManager.isHeadlessSystemUserMode()) {
+                // We assume that isDefaultHeadlessSystemUserMode() does not change during the OTA
+                // from userVersion < 8 since it is documented that pre-R devices do not support its
+                // modification. Therefore, its current value should be the same as the pre-update
+                // version.
+                if (!isDefaultHeadlessSystemUserMode()) {
                     userData.info.flags |= UserInfo.FLAG_FULL;
                 }
                 userIdsToWrite.add(userData.info.id);
@@ -3742,7 +3756,7 @@
         int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
                 | UserInfo.FLAG_PRIMARY;
         // Create the system user
-        String systemUserType = UserManager.isHeadlessSystemUserMode()
+        String systemUserType = isDefaultHeadlessSystemUserMode()
                 ? UserManager.USER_TYPE_SYSTEM_HEADLESS
                 : UserManager.USER_TYPE_FULL_SYSTEM;
         flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
@@ -6210,9 +6224,12 @@
                 com.android.internal.R.bool.config_guestUserEphemeral));
         pw.println("  Force ephemeral users: " + mForceEphemeralUsers);
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
-        final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode();
+        final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
         pw.println("  Is headless-system mode: " + isHeadlessSystemUserMode);
-        if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) {
+        if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
+            pw.println("  (differs from the current default build value)");
+        }
+        if (!TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
             pw.println("  (emulated by 'cmd user set-system-user-mode-emulation')");
             if (mUpdatingSystemUserMode) {
                 pw.println("  (and being updated after boot)");
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b98d20e..5b6196f 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,7 +128,8 @@
                 .setIsCredentialSharableWithParent(true)
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT));
     }
 
     /**
@@ -163,7 +164,8 @@
                 .setIsCredentialSharableWithParent(true)
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java b/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
index fa08add..227a3a1 100644
--- a/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
+++ b/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
@@ -39,6 +39,7 @@
     // These need to be kept in sync with system/security/ondevice-signing/StatsReporter.{h, cpp}.
     private static final String METRICS_FILE = "/data/misc/odsign/metrics/odsign-metrics.txt";
     private static final String COMPOS_METRIC_NAME = "comp_os_artifacts_check_record";
+    private static final String ODSIGN_METRIC_NAME = "odsign_record";
 
     /**
      * Arrange for stats to be uploaded in the background.
@@ -64,18 +65,45 @@
             for (String line : lines.split("\n")) {
                 String[] metrics = line.split(" ");
 
-                if (metrics.length != 4 || !metrics[0].equals(COMPOS_METRIC_NAME)) {
-                    Slog.w(TAG, "Malformed metrics file");
-                    break;
+                if (line.isEmpty() || metrics.length < 1) {
+                    Slog.w(TAG, "Empty metrics line");
+                    continue;
                 }
 
-                boolean currentArtifactsOk = metrics[1].equals("1");
-                boolean compOsPendingArtifactsExists = metrics[2].equals("1");
-                boolean useCompOsGeneratedArtifacts = metrics[3].equals("1");
+                switch (metrics[0]) {
+                    case COMPOS_METRIC_NAME: {
+                        if (metrics.length != 4) {
+                            Slog.w(TAG, "Malformed CompOS metrics line '" + line + "'");
+                            continue;
+                        }
 
-                ArtStatsLog.write(ArtStatsLog.EARLY_BOOT_COMP_OS_ARTIFACTS_CHECK_REPORTED,
-                        currentArtifactsOk, compOsPendingArtifactsExists,
-                        useCompOsGeneratedArtifacts);
+                        boolean currentArtifactsOk = metrics[1].equals("1");
+                        boolean compOsPendingArtifactsExists = metrics[2].equals("1");
+                        boolean useCompOsGeneratedArtifacts = metrics[3].equals("1");
+
+                        ArtStatsLog.write(ArtStatsLog.EARLY_BOOT_COMP_OS_ARTIFACTS_CHECK_REPORTED,
+                                currentArtifactsOk, compOsPendingArtifactsExists,
+                                useCompOsGeneratedArtifacts);
+                        break;
+                    }
+                    case ODSIGN_METRIC_NAME: {
+                        if (metrics.length != 2) {
+                            Slog.w(TAG, "Malformed odsign metrics line '" + line + "'");
+                            continue;
+                        }
+
+                        try {
+                            int status = Integer.parseInt(metrics[1]);
+                            ArtStatsLog.write(ArtStatsLog.ODSIGN_REPORTED, status);
+                        } catch (NumberFormatException e) {
+                            Slog.w(TAG, "Malformed odsign metrics line '" + line + "'");
+                        }
+
+                        break;
+                    }
+                    default:
+                        Slog.w(TAG, "Malformed metrics line '" + line + "'");
+                }
             }
         } catch (FileNotFoundException e) {
             // This is normal and probably means no new metrics have been generated.
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 8772de3..a7d4cea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.Attribution;
@@ -79,11 +78,8 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
-import libcore.util.EmptyArray;
-
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -109,21 +105,9 @@
     public static PackageInfo generate(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
-                grantedPermissions, state, userId, null, pkgSetting);
-    }
-
-    /**
-     * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
-     * @deprecated Once ENABLE_FEATURE_SCAN_APEX is removed, this should also be removed.
-     */
-    @Deprecated
-    @Nullable
-    public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, long flags,
-            @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
-        return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
+                grantedPermissions, state, userId, pkgSetting);
     }
 
     /**
@@ -132,8 +116,7 @@
     private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable ApexInfo apexInfo,
-            @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
                 pkgSetting);
         if (applicationInfo == null) {
@@ -247,22 +230,6 @@
                     &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
         }
 
-        if (apexInfo != null) {
-            File apexFile = new File(apexInfo.modulePath);
-
-            info.applicationInfo.sourceDir = apexFile.getPath();
-            info.applicationInfo.publicSourceDir = apexFile.getPath();
-            info.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-            info.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
-            if (apexInfo.isFactory) {
-                info.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-            } else {
-                info.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-            }
-            info.isApex = true;
-            info.isActiveApex = apexInfo.isActive;
-        }
-
         final SigningDetails signingDetails = pkg.getSigningDetails();
         // deprecated method of getting signing certificates
         if ((flags & PackageManager.GET_SIGNATURES) != 0) {
@@ -294,7 +261,7 @@
         info.coreApp = pkg.isCoreApp();
         info.isApex = pkg.isApex();
 
-        if (pkgSetting != null && !pkgSetting.hasSharedUser()) {
+        if (!pkgSetting.hasSharedUser()) {
             // It is possible that this shared UID app has left
             info.sharedUserId = null;
             info.sharedUserLabel = 0;
@@ -452,7 +419,7 @@
     public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
             @PackageManager.ApplicationInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @UserIdInt int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (pkg == null) {
             return null;
         }
@@ -463,35 +430,31 @@
         }
 
         // Make shallow copy so we can store the metadata/libraries safely
-        ApplicationInfo info = AndroidPackageUtils.toAppInfoWithoutState(pkg);
+        ApplicationInfo info = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
 
         updateApplicationInfo(info, flags, state);
 
         initForUser(info, pkg, userId);
 
-        if (pkgSetting != null) {
-            // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
-            PackageStateUnserialized pkgState = pkgSetting.getTransientState();
-            info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
-            List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
-            var usesLibraries = pkgState.getUsesLibraryInfos();
-            var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
-            for (int index = 0; index < usesLibraries.size(); index++) {
-                usesLibraryInfos.add(usesLibraries.get(index).getInfo());
-            }
-            info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
-                    ? null : usesLibraryFiles.toArray(new String[0]);
-            info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
-            if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
-                info.category = pkgSetting.getCategoryOverride();
-            }
+        // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
+        PackageStateUnserialized pkgState = pkgSetting.getTransientState();
+        info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
+        List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
+        var usesLibraries = pkgState.getUsesLibraryInfos();
+        var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+        for (int index = 0; index < usesLibraries.size(); index++) {
+            usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+        }
+        info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+                ? null : usesLibraryFiles.toArray(new String[0]);
+        info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+        if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+            info.category = pkgSetting.getCategoryOverride();
         }
 
         info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
-        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
-                : pkgSetting.getPrimaryCpuAbi();
-        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
-                : pkgSetting.getSecondaryCpuAbi();
+        info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
 
         info.flags |= appInfoFlags(info.flags, pkgSetting);
         info.privateFlags |= appInfoPrivateFlags(info.privateFlags, pkgSetting);
@@ -508,7 +471,7 @@
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
             @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @UserIdInt int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
     }
 
@@ -520,7 +483,7 @@
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
             @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         if (a == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -597,7 +560,7 @@
     @Nullable
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
     }
 
@@ -609,7 +572,7 @@
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @Nullable ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (s == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -647,7 +610,7 @@
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @NonNull ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (p == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -696,7 +659,7 @@
     @Nullable
     public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
             AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags,
-            PackageUserStateInternal state, int userId, @Nullable PackageStateInternal pkgSetting) {
+            PackageUserStateInternal state, int userId, @NonNull PackageStateInternal pkgSetting) {
         if (i == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -719,10 +682,8 @@
 
         initForUser(info, pkg, userId);
 
-        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
-                : pkgSetting.getPrimaryCpuAbi();
-        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
-                : pkgSetting.getSecondaryCpuAbi();
+        info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
         info.nativeLibraryDir = pkg.getNativeLibraryDir();
         info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
 
@@ -820,12 +781,11 @@
      * all uninstalled and hidden packages as well.
      */
     public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageUserStateInternal state,
+            @NonNull PackageStateInternal pkgSetting, PackageUserStateInternal state,
             @PackageManager.PackageInfoFlagsBits long flags) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
                 && !state.isInstalled()
-                && pkgSetting != null
                 && pkgSetting.getTransientState().isHiddenUntilInstalled()) {
             return false;
         }
@@ -878,7 +838,7 @@
 
     private static void assignFieldsComponentInfoParsedMainComponent(
             @NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
-            @Nullable PackageStateInternal pkgSetting, int userId) {
+            @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
         assignFieldsComponentInfoParsedMainComponent(info, component);
         Pair<CharSequence, Integer> labelAndIcon =
                 ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -889,7 +849,7 @@
 
     private static void assignFieldsPackageItemInfoParsedComponent(
             @NonNull PackageItemInfo info, @NonNull ParsedComponent component,
-            @Nullable PackageStateInternal pkgSetting, int userId) {
+            @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
         assignFieldsPackageItemInfoParsedComponent(info, component);
         Pair<CharSequence, Integer> labelAndIcon =
                 ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -1141,7 +1101,7 @@
         @Nullable
         public ApplicationInfo generate(AndroidPackage pkg,
                 @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
-                int userId, @Nullable PackageStateInternal pkgSetting) {
+                int userId, @NonNull PackageStateInternal pkgSetting) {
             ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
             if (appInfo != null) {
                 return appInfo;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index a6f1b29..5b0cc51 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -321,8 +321,4 @@
         info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
         info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
     }
-
-    public static ApplicationInfo toAppInfoWithoutState(AndroidPackage pkg) {
-        return ((ParsingPackageHidden) pkg).toAppInfoWithoutState();
-    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 661161f..2a65a01 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -16,17 +16,18 @@
 
 package com.android.server.pm.permission;
 
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.permission.PermissionControllerManager;
 import android.provider.DeviceConfig;
 import android.util.Log;
@@ -48,7 +49,7 @@
             "one_time_permissions_killed_delay_millis";
 
     private final @NonNull Context mContext;
-    private final @NonNull ActivityManager mActivityManager;
+    private final @NonNull IActivityManager mIActivityManager;
     private final @NonNull AlarmManager mAlarmManager;
     private final @NonNull PermissionControllerManager mPermissionControllerManager;
 
@@ -78,50 +79,15 @@
 
     OneTimePermissionUserManager(@NonNull Context context) {
         mContext = context;
-        mActivityManager = context.getSystemService(ActivityManager.class);
+        mIActivityManager = ActivityManager.getService();
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mPermissionControllerManager = new PermissionControllerManager(
                 mContext, PermissionThread.getHandler());
         mHandler = context.getMainThreadHandler();
     }
 
-    /**
-     * Starts a one-time permission session for a given package. A one-time permission session is
-     * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
-     * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
-     * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
-     * until going > importanceToResetTimer.
-     * <p>
-     * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
-     * then the session is extended until either the importance goes above
-     * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
-     * will continue the session and reset the timer.
-     * </p>
-     * <p>
-     * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
-     * </p>
-     * <p>
-     * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout
-     * is invoked.
-     * </p>
-     * <p>
-     * Note that if there is currently an active session for a package a new one isn't created and
-     * the existing one isn't changed.
-     * </p>
-     * @param packageName The package to start a one-time permission session for
-     * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
-     * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies
-     *                                     before ending the session. Set to -1 to use default value
-     *                                     for the device.
-     * @param importanceToResetTimer The least important level to uid must be to reset the timer
-     * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
-     *                                     session alive
-     *
-     * @hide
-     */
     void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
-            long revokeAfterKilledDelayMillis, int importanceToResetTimer,
-            int importanceToKeepSessionAlive) {
+            long revokeAfterKilledDelayMillis) {
         int uid;
         try {
             uid = mContext.getPackageManager().getPackageUid(packageName, 0);
@@ -133,13 +99,11 @@
         synchronized (mLock) {
             PackageInactivityListener listener = mListeners.get(uid);
             if (listener != null) {
-                listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis,
-                        importanceToResetTimer, importanceToKeepSessionAlive);
+                listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis);
                 return;
             }
             listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
-                    revokeAfterKilledDelayMillis, importanceToResetTimer,
-                    importanceToKeepSessionAlive);
+                    revokeAfterKilledDelayMillis);
             mListeners.put(uid, listener);
         }
     }
@@ -184,34 +148,58 @@
 
         private static final long TIMER_INACTIVE = -1;
 
+        private static final int STATE_GONE = 0;
+        private static final int STATE_TIMER = 1;
+        private static final int STATE_ACTIVE = 2;
+
         private final int mUid;
         private final @NonNull String mPackageName;
         private long mTimeout;
         private long mRevokeAfterKilledDelay;
-        private int mImportanceToResetTimer;
-        private int mImportanceToKeepSessionAlive;
 
         private boolean mIsAlarmSet;
         private boolean mIsFinished;
 
         private long mTimerStart = TIMER_INACTIVE;
 
-        private final ActivityManager.OnUidImportanceListener mStartTimerListener;
-        private final ActivityManager.OnUidImportanceListener mSessionKillableListener;
-        private final ActivityManager.OnUidImportanceListener mGoneListener;
-
         private final Object mInnerLock = new Object();
         private final Object mToken = new Object();
+        private final IUidObserver.Stub mObserver = new IUidObserver.Stub() {
+            @Override
+            public void onUidGone(int uid, boolean disabled) {
+                if (uid == mUid) {
+                    PackageInactivityListener.this.updateUidState(STATE_GONE);
+                }
+            }
+
+            @Override
+            public void onUidStateChanged(int uid, int procState, long procStateSeq,
+                    int capability) {
+                if (uid == mUid) {
+                    if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+                            && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                        PackageInactivityListener.this.updateUidState(STATE_TIMER);
+                    } else {
+                        PackageInactivityListener.this.updateUidState(STATE_ACTIVE);
+                    }
+                }
+            }
+
+            public void onUidActive(int uid) {
+            }
+            public void onUidIdle(int uid, boolean disabled) {
+            }
+            public void onUidProcAdjChanged(int uid) {
+            }
+            public void onUidCachedChanged(int uid, boolean cached) {
+            }
+        };
 
         private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
-                long revokeAfterkilledDelay, int importanceToResetTimer,
-                int importanceToKeepSessionAlive) {
-
+                long revokeAfterkilledDelay) {
             Log.i(LOG_TAG,
                     "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
-                            + " killedDelay=" + revokeAfterkilledDelay
-                            + " importanceToResetTimer=" + importanceToResetTimer
-                            + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
+                            + " killedDelay=" + revokeAfterkilledDelay);
 
             mUid = uid;
             mPackageName = packageName;
@@ -221,27 +209,24 @@
                             DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY,
                             DEFAULT_KILLED_DELAY_MILLIS)
                     : revokeAfterkilledDelay;
-            mImportanceToResetTimer = importanceToResetTimer;
-            mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;
 
-            mStartTimerListener =
-                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
-            mSessionKillableListener =
-                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
-            mGoneListener =
-                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
+            try {
+                mIActivityManager.registerUidObserver(mObserver,
+                        ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE,
+                        ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+                        null);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
+                // Can't register uid observer, just revoke immediately
+                synchronized (mInnerLock) {
+                    onPackageInactiveLocked();
+                }
+            }
 
-            mActivityManager.addOnUidImportanceListener(mStartTimerListener,
-                    importanceToResetTimer);
-            mActivityManager.addOnUidImportanceListener(mSessionKillableListener,
-                    importanceToKeepSessionAlive);
-            mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED);
-
-            onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
+            updateUidState();
         }
 
-        public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis,
-                int importanceToResetTimer, int importanceToKeepSessionAlive) {
+        public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) {
             synchronized (mInnerLock) {
                 mTimeout = Math.min(mTimeout, timeoutMillis);
                 mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay,
@@ -250,63 +235,79 @@
                                 DeviceConfig.NAMESPACE_PERMISSIONS,
                                 PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS)
                                 : revokeAfterKilledDelayMillis);
-                mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer);
-                mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive,
-                        mImportanceToKeepSessionAlive);
                 Log.v(LOG_TAG,
                         "Updated params for " + mPackageName + ". timeout=" + mTimeout
-                                + " killedDelay=" + mRevokeAfterKilledDelay
-                                + " importanceToResetTimer=" + mImportanceToResetTimer
-                                + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive);
-                onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName));
+                                + " killedDelay=" + mRevokeAfterKilledDelay);
+                updateUidState();
             }
         }
 
-        private void onImportanceChanged(int uid, int importance) {
-            if (uid != mUid) {
-                return;
+        private int getCurrentState() {
+            try {
+                return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null));
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
             }
+            return STATE_GONE;
+        }
 
-            Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
-                    + " importance=" + importance);
+        private int getStateFromProcState(int procState) {
+            if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                return STATE_GONE;
+            } else {
+                if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+                    return STATE_TIMER;
+                } else {
+                    return STATE_ACTIVE;
+                }
+            }
+        }
+
+        private void updateUidState() {
+            updateUidState(getCurrentState());
+        }
+
+        private void updateUidState(int state) {
+            Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")."
+                    + " state=" + state);
             synchronized (mInnerLock) {
                 // Remove any pending inactivity callback
                 mHandler.removeCallbacksAndMessages(mToken);
 
-                if (importance > IMPORTANCE_CACHED) {
+                if (state == STATE_GONE) {
                     if (mRevokeAfterKilledDelay == 0) {
                         onPackageInactiveLocked();
                         return;
                     }
                     // Delay revocation in case app is restarting
                     mHandler.postDelayed(() -> {
-                        int imp = mActivityManager.getUidImportance(mUid);
-                        if (imp > IMPORTANCE_CACHED) {
-                            onPackageInactiveLocked();
-                        } else {
-                            if (DEBUG) {
-                                Log.d(LOG_TAG, "No longer gone after delayed revocation. "
-                                        + "Rechecking for " + mPackageName + " (" + mUid + ").");
+                        int currentState;
+                        synchronized (mInnerLock) {
+                            currentState = getCurrentState();
+                            if (currentState == STATE_GONE) {
+                                onPackageInactiveLocked();
+                                return;
                             }
-                            onImportanceChanged(mUid, imp);
                         }
+                        if (DEBUG) {
+                            Log.d(LOG_TAG, "No longer gone after delayed revocation. "
+                                    + "Rechecking for " + mPackageName + " (" + mUid
+                                    + ").");
+                        }
+                        updateUidState(currentState);
                     }, mToken, mRevokeAfterKilledDelay);
                     return;
-                }
-                if (importance > mImportanceToResetTimer) {
+                } else if (state == STATE_TIMER) {
                     if (mTimerStart == TIMER_INACTIVE) {
                         if (DEBUG) {
                             Log.d(LOG_TAG, "Start the timer for "
                                     + mPackageName + " (" + mUid + ").");
                         }
                         mTimerStart = System.currentTimeMillis();
+                        setAlarmLocked();
                     }
-                } else {
+                } else if (state == STATE_ACTIVE) {
                     mTimerStart = TIMER_INACTIVE;
-                }
-                if (importance > mImportanceToKeepSessionAlive) {
-                    setAlarmLocked();
-                } else {
                     cancelAlarmLocked();
                 }
             }
@@ -320,19 +321,9 @@
                 mIsFinished = true;
                 cancelAlarmLocked();
                 try {
-                    mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Could not remove start timer listener", e);
-                }
-                try {
-                    mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Could not remove session killable listener", e);
-                }
-                try {
-                    mActivityManager.removeOnUidImportanceListener(mGoneListener);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Could not remove gone listener", e);
+                    mIActivityManager.unregisterUidObserver(mObserver);
+                } catch (RemoteException e) {
+                    Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
                 }
             }
         }
@@ -396,9 +387,11 @@
                         mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
                                 mPackageName);
                     });
-            mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
-            mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
-            mActivityManager.removeOnUidImportanceListener(mGoneListener);
+            try {
+                mIActivityManager.unregisterUidObserver(mObserver);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
+            }
             synchronized (mLock) {
                 mListeners.remove(mUid);
             }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 1fa3b3b..5f9ab95 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -385,8 +385,7 @@
 
     @Override
     public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
-            long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer,
-            int importanceToKeepSessionAlive) {
+            long timeoutMillis, long revokeAfterKilledDelayMillis) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
                 "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
@@ -396,8 +395,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
-                    timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
-                    importanceToKeepSessionAlive);
+                    timeoutMillis, revokeAfterKilledDelayMillis);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index ffb652e..9a66673 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -223,9 +223,11 @@
                 this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
 
         mAppOpsCallback = new IAppOpsCallback.Stub() {
-            public void opChanged(int op, int uid, String packageName) {
-                synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
-                        UserHandle.getUserId(uid));
+            public void opChanged(int op, int uid, @Nullable String packageName) {
+                if (packageName != null) {
+                    synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
+                            UserHandle.getUserId(uid));
+                }
                 resetAppOpPermissionsIfNotRequestedForUidAsync(uid);
             }
         };
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index 0c5cc15..08800da 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -1,4 +1,4 @@
-#Rollback Manager
+# Rollback Manager
 
 ## Introduction
 
@@ -7,9 +7,9 @@
 APEX update to the previous version installed on the device, and reverting any
 APK or APEX data to the state it was in at the time of install.
 
-##Rollback Basics
+## Rollback Basics
 
-###How Rollbacks Work
+### How Rollbacks Work
 
 A new install parameter ENABLE_ROLLBACK can be specified to enable rollback when
 updating an application. For example:
@@ -42,27 +42,27 @@
 
 See below for more details of shell commands for rollback.
 
-###Rollback Triggers
+### Rollback Triggers
 
-####Manually Triggered Rollback
+#### Manually Triggered Rollback
 
 As mentioned above, it is possible to trigger rollback on device using a shell
 command. This is for testing purposes only. We do not expect this mechanism to
 be used in production in practice.
 
-####Watchdog Triggered Rollback
+#### Watchdog Triggered Rollback
 
 Watchdog triggered rollback is intended to address severe issues with the
 device. The platform provides several different watchdogs that can trigger
 rollback.
 
-#####Package Watchdog
+##### Package Watchdog
 
 There is a package watchdog service running on device that will trigger rollback
 of an update if there are 5 ANRs or process crashes within a 1 minute window for
 a package in the update.
 
-#####Native Watchdog
+##### Native Watchdog
 
 If a native service crashes repeatedly after an update is installed, rollback
 will be triggered. This particularly applies to updates that include APEXes
@@ -70,25 +70,25 @@
 native services have been affected by an update, *any* crashing native service
 will cause the rollback to be triggered.
 
-#####Explicit Health Check
+##### Explicit Health Check
 
 There is an explicit check to verify the network stack is functional after an
 update. If there is no network connectivity within a certain time period after
 an update, rollback is triggered.
 
-####Server Triggered Rollback
+#### Server Triggered Rollback
 The RollbackManager API may be used by the installer to roll back an update
 based on a request from the server.
 
-##Rollback Details
+## Rollback Details
 
-###RollbackManager API
+### RollbackManager API
 
 The RollbackManager API is an @SystemAPI guarded by the MANAGE_ROLLBACKS and
 TEST_MANAGE_ROLLBACKS permissions. See RollbackManager.java for details about
 the RollbackManager API.
 
-###Rollback of APEX modules
+### Rollback of APEX modules
 
 Rollback is supported for APEX modules in addition to APK modules. In Q, there
 was no concept of data associated with an APEX, so only the APEX itself is
@@ -100,7 +100,7 @@
 directories). For example, FooV2.apex must not change the file format of some
 state stored on the device in such a way that FooV1.apex cannot read the file.
 
-###Rollback of MultiPackage Installs
+### Rollback of MultiPackage Installs
 
 Rollback can be enabled for multi-package installs. This requires that all
 packages in the install session, including the parent session, have the
@@ -119,7 +119,7 @@
 install session, rollback will not be enabled for any package in the
 multi-package install session.
 
-###Rollback of Staged Installs
+### Rollback of Staged Installs
 
 Rollback can be enabled for staged installs, which require reboot to take
 effect. If reboot was required when the package was updated, then reboot is
@@ -127,21 +127,21 @@
 package was updated, then no reboot is required when the package is rolled back.
 
 
-###Rollbacks on Multi User Devices
+### Rollbacks on Multi User Devices
 
 Rollbacks should work properly on devices with multiple users. There is special
 handling of user data backup to ensure app user data is properly backed up and
 restored for all users, even for credential encrypted users that have not been
 unlocked at various points during the flow.
 
-###Rollback whitelist
+### Rollback whitelist
 
 Outside of testing, rollback may only be enabled for packages listed in the
 sysconfig rollback whitelist - see
 `SystemConfig#getRollbackWhitelistedPackages`. Attempts to enable rollback for
 non-whitelisted packages will fail.
 
-###Failure to Enable Rollback
+### Failure to Enable Rollback
 
 There are a number of reasons why we may be unable to enable rollback for a
 package, including:
@@ -158,13 +158,13 @@
 rollback enabled. Failing to enable rollback does not cause the installation to
 fail.
 
-###Failure to Commit Rollback
+### Failure to Commit Rollback
 
 For the most part, a rollback will remain available after failure to commit it.
 This allows the caller to retry the rollback if they have reason to believe it
 will not fail again the next time the commit of the rollback is attempted.
 
-###Installing Previously Rolled Back Packages
+### Installing Previously Rolled Back Packages
 There is no logic in the platform itself to prevent installing a version of a
 package that was previously rolled back.
 
@@ -175,7 +175,7 @@
 installer to prevent reinstall of a previously rolled back package version if so
 desired.
 
-###Rollback Expiration
+### Rollback Expiration
 
 An available rollback is expired if the rollback lifetime has been exceeded or
 if there is a new update to package associated with the rollback. When an
@@ -183,9 +183,9 @@
 the rollback are deleted. Once a rollback is expired, it can no longer be
 executed.
 
-##Shell Commands for Rollback
+## Shell Commands for Rollback
 
-###Installing an App with Rollback Enabled
+### Installing an App with Rollback Enabled
 
 The `adb install` command accepts the `--enable-rollback` flag to install an app
 with rollback enabled. For example:
@@ -194,7 +194,7 @@
 $ adb install --enable-rollback FooV2.apk
 ```
 
-###Triggering Rollback Manually
+### Triggering Rollback Manually
 
 If rollback is available for an application, the pm command can be used to
 trigger rollback manually on device:
@@ -206,7 +206,7 @@
 For rollback of staged installs, you have to manually reboot the device for the
 rollback to take effect after running the 'pm rollback-app' command.
 
-###Listing the Status of Rollbacks on Device
+### Listing the Status of Rollbacks on Device
 
 You can get a list with details about available and recently committed rollbacks
 using dumpsys. For example:
@@ -246,9 +246,9 @@
 The list of rollbacks is also included in bug reports. Search for "DUMP OF
 SERVICE rollback".
 
-##Configuration Properties
+## Configuration Properties
 
-###Rollback Lifetime
+### Rollback Lifetime
 
 Rollback lifetime refers to the maximum duration of time after the rollback is
 first enabled that it will be available. The default is for rollbacks to be
@@ -263,7 +263,7 @@
 
 The update will not take effect until after system server has been restarted.
 
-###Enable Rollback Timeout
+### Enable Rollback Timeout
 
 The enable rollback timeout is how long RollbackManager is allowed to take to
 enable rollback when performing an update. This includes the time needed to make
@@ -279,7 +279,7 @@
 
 The update will take effect for the next install with rollback enabled.
 
-##Limitations
+## Limitations
 
 * You cannot enable rollback for the first version of an application installed
 on the device. Only updates to a package previously installed on the device can
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 61c21e6..ab35dc8 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -35,6 +35,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
+import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.hardware.SensorPrivacyManager.Sources.DIALOG;
@@ -664,6 +665,29 @@
                             .build());
         }
 
+        private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor,
+                @SensorPrivacyManager.ToggleType int toggleType) {
+            String activityName = mContext.getResources().getString(
+                    R.string.config_sensorStateChangedActivity);
+            if (TextUtils.isEmpty(activityName)) {
+                return;
+            }
+
+            Intent dialogIntent = new Intent();
+            dialogIntent.setComponent(
+                    ComponentName.unflattenFromString(activityName));
+
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setTaskOverlay(true, true);
+
+            dialogIntent.addFlags(
+                    FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION);
+
+            dialogIntent.putExtra(EXTRA_SENSOR, sensor);
+            dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType);
+            mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM);
+        }
+
         private boolean isTelevision(Context context) {
             int uiMode = context.getResources().getConfiguration().uiMode;
             return (uiMode & Configuration.UI_MODE_TYPE_MASK)
@@ -1378,6 +1402,8 @@
                     mToggleSensorListeners.finishBroadcast();
                 }
             }
+
+            mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
         }
 
         public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 5b341ce..43ffa81 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -22,7 +22,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -158,7 +158,7 @@
     /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
     void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails);
 
     /** @see com.android.internal.statusbar.IStatusBar#showTransient */
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c02ebfd..7281a47 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -83,7 +83,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -617,15 +618,15 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
-                    navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                    navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
             if (mBar != null) {
                 try {
                     mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                            navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                            navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                             letterboxDetails);
                 } catch (RemoteException ex) { }
             }
@@ -1211,7 +1212,7 @@
         private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
         private boolean mNavbarColorManagedByIme = false;
         private @Behavior int mBehavior;
-        private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+        private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
         private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
@@ -1223,14 +1224,14 @@
 
         private void setBarAttributes(@Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName,
                 LetterboxDetails[] letterboxDetails) {
             mAppearance = appearance;
             mAppearanceRegions = appearanceRegions;
             mNavbarColorManagedByIme = navbarColorManagedByIme;
             mBehavior = behavior;
-            mRequestedVisibilities = requestedVisibilities;
+            mRequestedVisibleTypes = requestedVisibleTypes;
             mPackageName = packageName;
             mLetterboxDetails = letterboxDetails;
         }
@@ -1363,7 +1364,7 @@
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
                     state.mImeBackDisposition, state.mShowImeSwitcher,
                     gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
-                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
+                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
                     state.mPackageName, transientBarTypes, state.mLetterboxDetails);
         }
     }
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4972412..8d106f7 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -28,7 +28,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -60,10 +60,10 @@
 
     @Override
     public void setConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
-        ConfigurationChangeListener configurationChangeListener =
+            @NonNull StateChangeListener listener) {
+        StateChangeListener stateChangeListener =
                 () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
+        mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 773b517..e9827ce 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -25,8 +25,8 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -185,8 +185,7 @@
      * ensure O(1) lookup performance when working out whether a listener should trigger.
      */
     @GuardedBy("mListeners")
-    private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
-            new ArrayMap<>();
+    private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>();
 
     private static final Object SLOCK = new Object();
 
@@ -213,7 +212,7 @@
 
     private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
         synchronized (mListeners) {
-            for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
+            for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
                     : mListeners.entrySet()) {
                 // It's unclear which set of the following two Sets is going to be larger in the
                 // average case: monitoredKeys will be a subset of the set of possible keys, but
@@ -249,7 +248,7 @@
      * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
      * associated remove method.
      */
-    public void addListener(@NonNull ConfigurationChangeListener listener,
+    public void addListener(@NonNull StateChangeListener listener,
             @NonNull Set<String> keys) {
         Objects.requireNonNull(listener);
         Objects.requireNonNull(keys);
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index a39f64c..ff180eb 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -19,7 +19,7 @@
 import android.annotation.UserIdInt;
 import android.app.time.TimeConfiguration;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 /**
  * An interface that provides access to service configuration for time detection. This hides
@@ -33,18 +33,18 @@
      * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
      * The listener is invoked on the main thread.
      */
-    void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+    void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Removes a listener previously added via {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+     * #addConfigurationInternalChangeListener(StateChangeListener)}.
      */
-    void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+    void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
      * snapshot so callers must use {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+     * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
      * changes.
      */
     @NonNull
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 71acf35..4ef713c 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -49,7 +49,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -104,8 +104,8 @@
     @NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
 
     @GuardedBy("this")
-    @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
-            new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
 
     /**
      * If a newly calculated system clock time and the current system clock time differs by this or
@@ -167,20 +167,20 @@
     }
 
     private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
             changeListener.onChange();
         }
     }
 
     @Override
     public synchronized void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
     }
 
     @Override
     public synchronized void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
     }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 3cee19c..13ec753 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -44,8 +44,8 @@
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timezonedetector.ArrayMapWithHistory;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ReferenceWithHistory;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.io.PrintWriter;
 import java.time.Duration;
@@ -136,11 +136,11 @@
     public interface Environment {
 
         /**
-         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
-         * changes that could affect the content of {@link ConfigurationInternal}.
+         * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
+         * could affect the content of {@link ConfigurationInternal}.
          * This is invoked during system server setup.
          */
-        void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+        void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
         /** Returns the {@link ConfigurationInternal} for the current user. */
         @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 8e2a5f4..0409a84 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -26,7 +26,6 @@
 import android.annotation.UserIdInt;
 import android.app.time.Capabilities.CapabilityState;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 import android.os.UserHandle;
 
@@ -75,7 +74,7 @@
         mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
         mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
-        mUserId = builder.mUserId;
+        mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set");
         mUserConfigAllowed = builder.mUserConfigAllowed;
         mLocationEnabledSetting = builder.mLocationEnabledSetting;
         mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
@@ -151,8 +150,7 @@
      * Returns true if the user is allowed to modify time zone configuration, e.g. can be false due
      * to device policy (enterprise).
      *
-     * <p>See also {@link #createCapabilitiesAndConfig(boolean)} for situations where this value
-     * are ignored.
+     * <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored.
      */
     public boolean isUserConfigAllowed() {
         return mUserConfigAllowed;
@@ -196,20 +194,8 @@
                 || getGeoDetectionRunInBackgroundEnabled());
     }
 
-    /**
-     * Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values.
-     *
-     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
-     *   policy restrictions that should apply to actual users can be ignored
-     */
-    public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
-            boolean bypassUserPolicyChecks) {
-        return new TimeZoneCapabilitiesAndConfig(
-                asCapabilities(bypassUserPolicyChecks), asConfiguration());
-    }
-
     @NonNull
-    private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
+    public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
         UserHandle userHandle = UserHandle.of(mUserId);
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
 
@@ -262,7 +248,7 @@
     }
 
     /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
-    private TimeZoneConfiguration asConfiguration() {
+    public TimeZoneConfiguration asConfiguration() {
         return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
                 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
@@ -335,8 +321,7 @@
      */
     public static class Builder {
 
-        private final @UserIdInt int mUserId;
-
+        private @UserIdInt Integer mUserId;
         private boolean mUserConfigAllowed;
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
@@ -348,11 +333,9 @@
         private boolean mGeoDetectionEnabledSetting;
 
         /**
-         * Creates a new Builder with only the userId set.
+         * Creates a new Builder.
          */
-        public Builder(@UserIdInt int userId) {
-            mUserId = userId;
-        }
+        public Builder() {}
 
         /**
          * Creates a new Builder by copying values from an existing instance.
@@ -371,6 +354,14 @@
         }
 
         /**
+         * Sets the user ID the configuration is for.
+         */
+        public Builder setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        /**
          * Sets whether the user is allowed to configure time zone settings on this device.
          */
         public Builder setUserConfigAllowed(boolean configAllowed) {
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 4749f73..5cb48c2 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,8 +18,6 @@
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 
@@ -29,7 +27,6 @@
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 
 /**
  * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
@@ -38,29 +35,7 @@
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-
-    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
-        mContext = Objects.requireNonNull(context);
-        mHandler = Objects.requireNonNull(handler);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
-    }
-
-    @Override
-    public void setConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
-        ConfigurationChangeListener configurationChangeListener =
-                () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
-    }
-
-    @Override
-    @NonNull
-    public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+    EnvironmentImpl() {
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 8da5d6a..4ac2ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -59,20 +59,18 @@
      * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
      * The listener is invoked on the main thread.
      */
-    void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener);
+    void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Removes a listener previously added via {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+     * #addConfigurationInternalChangeListener(StateChangeListener)}.
      */
-    void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener);
+    void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
      * snapshot so callers must use {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+     * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
      * changes.
      */
     @NonNull
@@ -104,8 +102,7 @@
      *
      * <p>Note: Currently only for use by long-lived objects; there is no associated remove method.
      */
-    void addLocationTimeZoneManagerConfigListener(
-            @NonNull ConfigurationChangeListener listener);
+    void addLocationTimeZoneManagerConfigListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns {@code true} if the telephony-based time zone detection feature is supported on the
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index e2f4246..dfb9619 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -22,7 +22,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -104,8 +103,8 @@
     @NonNull private final LocationManager mLocationManager;
 
     @GuardedBy("this")
-    @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
-            new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
 
     /**
      * The mode to use for the primary location time zone provider in a test. Setting this
@@ -207,20 +206,20 @@
     }
 
     private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
             changeListener.onChange();
         }
     }
 
     @Override
     public synchronized void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
     }
 
     @Override
     public synchronized void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
     }
 
@@ -237,10 +236,10 @@
             @NonNull TimeZoneConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
         Objects.requireNonNull(requestedConfiguration);
 
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
-                .createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
+        ConfigurationInternal configurationInternal = getConfigurationInternal(userId);
+        TimeZoneCapabilities capabilities =
+                configurationInternal.asCapabilities(bypassUserPolicyChecks);
+        TimeZoneConfiguration oldConfiguration = configurationInternal.asConfiguration();
 
         final TimeZoneConfiguration newConfiguration =
                 capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
@@ -292,7 +291,8 @@
     @Override
     @NonNull
     public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
-        return new ConfigurationInternal.Builder(userId)
+        return new ConfigurationInternal.Builder()
+                .setUserId(userId)
                 .setTelephonyDetectionFeatureSupported(
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
@@ -354,7 +354,7 @@
 
     @Override
     public void addLocationTimeZoneManagerConfigListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
rename to services/core/java/com/android/server/timezonedetector/StateChangeListener.java
index aa8ad37..2b5639c 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
+++ b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
@@ -17,11 +17,11 @@
 package com.android.server.timezonedetector;
 
 /**
- * A listener used to receive notification that configuration has / may have changed (depending on
+ * A listener used to receive notification that state has / may have changed (depending on
  * the usecase).
  */
 @FunctionalInterface
-public interface ConfigurationChangeListener {
-    /** Called when the configuration may have changed. */
+public interface StateChangeListener {
+    /** Called when something (may have) changed. */
     void onChange();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index ce64eac..dfb44df 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -35,17 +35,14 @@
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final CurrentUserIdentityInjector mCurrentUserIdentityInjector;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
     public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
             @NonNull CurrentUserIdentityInjector currentUserIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCurrentUserIdentityInjector = Objects.requireNonNull(currentUserIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
     }
 
@@ -53,10 +50,9 @@
     @NonNull
     public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfigForDpm() {
         int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
-        ConfigurationInternal configurationInternal =
-                mServiceConfigAccessor.getConfigurationInternal(currentUserId);
         final boolean bypassUserPolicyChecks = true;
-        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+        return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                currentUserId, bypassUserPolicyChecks);
     }
 
     @Override
@@ -65,7 +61,7 @@
 
         int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
         final boolean bypassUserPolicyChecks = true;
-        return mServiceConfigAccessor.updateConfiguration(
+        return mTimeZoneDetectorStrategy.updateConfiguration(
                 currentUserId, configuration, bypassUserPolicyChecks);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 13f1694..f415cf0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -83,7 +83,7 @@
             ServiceConfigAccessor serviceConfigAccessor =
                     ServiceConfigAccessorImpl.getInstance(context);
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                    TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+                    TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
             DeviceActivityMonitor deviceActivityMonitor =
                     DeviceActivityMonitorImpl.create(context, handler);
 
@@ -99,16 +99,14 @@
             CurrentUserIdentityInjector currentUserIdentityInjector =
                     CurrentUserIdentityInjector.REAL;
             TimeZoneDetectorInternal internal = new TimeZoneDetectorInternalImpl(
-                    context, handler, currentUserIdentityInjector, serviceConfigAccessor,
-                    timeZoneDetectorStrategy);
+                    context, handler, currentUserIdentityInjector, timeZoneDetectorStrategy);
             publishLocalService(TimeZoneDetectorInternal.class, internal);
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
             CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeZoneDetectorService service = new TimeZoneDetectorService(
-                    context, handler, callerIdentityInjector, serviceConfigAccessor,
-                    timeZoneDetectorStrategy);
+                    context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
 
             // Dump the device activity monitor when the service is dumped.
             service.addDumpable(deviceActivityMonitor);
@@ -127,9 +125,6 @@
     private final CallerIdentityInjector mCallerIdentityInjector;
 
     @NonNull
-    private final ServiceConfigAccessor mServiceConfigAccessor;
-
-    @NonNull
     private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
     /**
@@ -150,18 +145,16 @@
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
 
         // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
-        // the configuration changes for any reason.
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(
-                () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+        // the detector state changes for any reason.
+        mTimeZoneDetectorStrategy.addChangeListener(
+                () -> mHandler.post(this::handleChangeOnHandlerThread));
     }
 
     @Override
@@ -174,12 +167,15 @@
     TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
         enforceManageTimeZoneDetectorPermission();
 
+        // Resolve constants like USER_CURRENT to the true user ID as needed.
+        int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
+
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            ConfigurationInternal configurationInternal =
-                    mServiceConfigAccessor.getConfigurationInternal(userId);
             final boolean bypassUserPolicyChecks = false;
-            return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+            return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                    resolvedUserId, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -204,7 +200,7 @@
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             final boolean bypassUserPolicyChecks = false;
-            return mServiceConfigAccessor.updateConfiguration(
+            return mTimeZoneDetectorStrategy.updateConfiguration(
                     resolvedUserId, configuration, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
@@ -285,8 +281,9 @@
         }
     }
 
-    void handleConfigurationInternalChangedOnHandlerThread() {
-        // Configuration has changed, but each user may have a different view of the configuration.
+    void handleChangeOnHandlerThread() {
+        // Detector state has changed. Each user may have a different view of the configuration so
+        // no information is passed; each client must query what they're interested in.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
         synchronized (mListeners) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 69284e3..328cf72 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -94,6 +96,48 @@
  */
 public interface TimeZoneDetectorStrategy extends Dumpable {
 
+    /**
+     * Adds a listener that will be triggered when something changes that could affect the result
+     * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+     * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+     * can monitor for changes to data derived from {@link TimeZoneCapabilitiesAndConfig} and update
+     * the UI accordingly.
+     */
+    void addChangeListener(StateChangeListener listener);
+
+    /**
+     * Returns a {@link TimeZoneCapabilitiesAndConfig} object for the specified user.
+     *
+     * <p>The strategy is dependent on device state like current user, settings and device config.
+     * These updates are usually handled asynchronously, so callers should expect some delay between
+     * a change being made directly to services like settings and the strategy becoming aware of
+     * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+     *
+     * @param userId the user ID to retrieve the information for
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+    /**
+     * Updates the configuration properties that control a device's time zone behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed, {@code false}
+     * otherwise.
+     *
+     * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+     * subsequent calls.
+     *
+     * @param userId the current user ID, supplied to make sure that the asynchronous process
+     *   that happens when users switch is completed when the call is made
+     * @param configuration the configuration changes
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    boolean updateConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration,
+            boolean bypassUserPolicyChecks);
+
     /** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
     @NonNull
     TimeZoneState getTimeZoneState();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 18c8885..ecf25e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -31,10 +31,10 @@
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.content.Context;
 import android.os.Handler;
 import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
@@ -46,6 +46,7 @@
 
 import java.io.PrintWriter;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -68,16 +69,6 @@
     public interface Environment {
 
         /**
-         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
-         * changes that could affect the content of {@link ConfigurationInternal}.
-         * This is invoked during system server setup.
-         */
-        void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
-
-        /** Returns the {@link ConfigurationInternal} for the current user. */
-        @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
-        /**
          * Returns the device's currently configured time zone. May return an empty string.
          */
         @NonNull String getDeviceTimeZone();
@@ -206,6 +197,22 @@
     private final ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion =
             new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
+    @NonNull
+    private final ServiceConfigAccessor mServiceConfigAccessor;
+
+    /** The handler used for asynchronous operations triggered by this. */
+    @NonNull
+    private final Handler mStateChangeHandler;
+
+    @GuardedBy("this")
+    @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
+    /**
+     * A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
+     * because it is relatively heavyweight to obtain and is used more often than it is expected to
+     * change. Because many operations are asynchronous, this value may be out of date but should
+     * be "eventually consistent".
+     */
     @GuardedBy("this")
     @NonNull
     private ConfigurationInternal mCurrentConfigurationInternal;
@@ -229,29 +236,93 @@
      * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
      */
     public static TimeZoneDetectorStrategyImpl create(
-            @NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+            @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
 
-        Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
-        return new TimeZoneDetectorStrategyImpl(environment);
+        Environment environment = new EnvironmentImpl();
+        return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, handler, environment);
     }
 
     @VisibleForTesting
-    public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+    public TimeZoneDetectorStrategyImpl(
+            @NonNull ServiceConfigAccessor serviceConfigAccessor,
+            @NonNull Handler handler, @NonNull Environment environment) {
         mEnvironment = Objects.requireNonNull(environment);
+        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
+        mStateChangeHandler = Objects.requireNonNull(handler);
 
         // Start with telephony fallback enabled.
         mTelephonyTimeZoneFallbackEnabled =
                 new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
 
         synchronized (this) {
-            mEnvironment.setConfigurationInternalChangeListener(
-                    this::handleConfigurationInternalChanged);
-            mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+            // Listen for config and user changes and get an initial snapshot of configuration.
+            StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+            mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+            mCurrentConfigurationInternal =
+                    mServiceConfigAccessor.getCurrentUserConfigurationInternal();
         }
     }
 
     @Override
+    public synchronized TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal;
+        if (mCurrentConfigurationInternal.getUserId() == userId) {
+            // Use the cached snapshot we have.
+            configurationInternal = mCurrentConfigurationInternal;
+        } else {
+            // This is not a common case: It would be unusual to want the configuration for a user
+            // other than the "current" user, but it is supported because it is trivial to do so.
+            // Unlike the current user config, there's no cached copy to worry about so read it
+            // directly from mServiceConfigAccessor.
+            configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+        }
+        return new TimeZoneCapabilitiesAndConfig(
+                configurationInternal.asCapabilities(bypassUserPolicyChecks),
+                configurationInternal.asConfiguration());
+    }
+
+    @Override
+    public synchronized boolean updateConfiguration(
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration,
+            boolean bypassUserPolicyChecks) {
+
+        // Write-through
+        boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+
+        // The update above will trigger config update listeners asynchronously if they are needed,
+        // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+        // wouldn't see the update. So, handle the cache update and notifications here. When the
+        // async update listener triggers it will find everything already up to date and do nothing.
+        if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
+            ConfigurationInternal configurationInternal =
+                    mServiceConfigAccessor.getConfigurationInternal(userId);
+
+            // If the configuration actually changed, update the cached copy synchronously and do
+            // other necessary house-keeping / (async) listener notifications.
+            if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
+                mCurrentConfigurationInternal = configurationInternal;
+
+                String logMsg = "updateConfiguration:"
+                        + " userId=" + userId
+                        + ", configuration=" + configuration
+                        + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
+                        + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
+                logTimeZoneDebugInfo(logMsg);
+
+                handleConfigurationInternalChanged(logMsg);
+            }
+        }
+        return updateSuccessful;
+    }
+
+    @Override
+    public synchronized void addChangeListener(StateChangeListener listener) {
+        mStateChangeListeners.add(listener);
+    }
+
+    @Override
     public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
         Objects.requireNonNull(timeZoneId);
 
@@ -334,9 +405,8 @@
         String timeZoneId = suggestion.getZoneId();
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
 
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+        TimeZoneCapabilities capabilities =
+                currentUserConfig.asCapabilities(bypassUserPolicyChecks);
         if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
                     + ": capabilities=" + capabilities
@@ -735,18 +805,37 @@
         return findBestTelephonySuggestion();
     }
 
-    private synchronized void handleConfigurationInternalChanged() {
+    /**
+     * Handles a configuration change notification.
+     */
+    private synchronized void handleConfigurationInternalMaybeChanged() {
         ConfigurationInternal currentUserConfig =
-                mEnvironment.getCurrentUserConfigurationInternal();
-        String logMsg = "handleConfigurationInternalChanged:"
-                + " oldConfiguration=" + mCurrentConfigurationInternal
-                + ", newConfiguration=" + currentUserConfig;
-        logTimeZoneDebugInfo(logMsg);
-        mCurrentConfigurationInternal = currentUserConfig;
+                mServiceConfigAccessor.getCurrentUserConfigurationInternal();
 
-        // The configuration change may have changed available suggestions or the way suggestions
-        // are used, so re-run detection.
-        doAutoTimeZoneDetection(currentUserConfig, logMsg);
+        // The configuration may not actually have changed so check before doing anything.
+        if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
+            String logMsg = "handleConfigurationInternalMaybeChanged:"
+                    + " oldConfiguration=" + mCurrentConfigurationInternal
+                    + ", newConfiguration=" + currentUserConfig;
+            logTimeZoneDebugInfo(logMsg);
+
+            mCurrentConfigurationInternal = currentUserConfig;
+
+            handleConfigurationInternalChanged(logMsg);
+        }
+    }
+
+    /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+    @GuardedBy("this")
+    private void handleConfigurationInternalChanged(@NonNull String logMsg) {
+        // Notify change listeners asynchronously.
+        for (StateChangeListener listener : mStateChangeListeners) {
+            mStateChangeHandler.post(listener::onChange);
+        }
+
+        // The configuration change may have changed available suggestions or the way
+        // suggestions are used, so re-run detection.
+        doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
     }
 
     /**
@@ -760,8 +849,7 @@
         ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
         final boolean bypassUserPolicyChecks = false;
         ipw.println("[Capabilities="
-                + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
-                + "]");
+                + mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
         ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
         ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
                 + mEnvironment.getDeviceTimeZoneConfidence());
@@ -824,6 +912,11 @@
         return mTelephonyTimeZoneFallbackEnabled.getValue();
     }
 
+    @VisibleForTesting
+    public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+        return mCurrentConfigurationInternal;
+    }
+
     /**
      * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
      */
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index 90540b0..b1fc4f5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -36,6 +36,7 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.service.timezone.TimeZoneProviderEvent;
+import android.service.timezone.TimeZoneProviderStatus;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -294,6 +295,12 @@
                     || stateEnum == PROVIDER_STATE_DESTROYED;
         }
 
+        /** Returns the status reported by the provider, if available. */
+        @Nullable
+        TimeZoneProviderStatus getReportedStatus() {
+            return event == null ? null : event.getTimeZoneProviderStatus();
+        }
+
         @Override
         public String toString() {
             // this.provider is omitted deliberately to avoid recursion, since the provider holds
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index e7d16c8..5eeafc1 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
@@ -20,9 +20,9 @@
 import android.annotation.NonNull;
 import android.os.SystemClock;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.time.Duration;
 import java.util.Objects;
@@ -35,7 +35,7 @@
         extends LocationTimeZoneProviderController.Environment {
 
     @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-    @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
+    @NonNull private final StateChangeListener mConfigurationInternalChangeListener;
 
     LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
             @NonNull ServiceConfigAccessor serviceConfigAccessor,
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index 8a6f927..aa2b74e 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -58,7 +58,7 @@
         if (hasInvalidZones(event)) {
             TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
                     event.getTimeZoneProviderStatus())
-                    .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                     .build();
             return TimeZoneProviderEvent.createUncertainEvent(
                     event.getCreationElapsedMillis(), providerStatus);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 6012993..d944a3b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -655,7 +655,8 @@
     }
 
     private void registerSettingsChangeReceiver(IntentFilter intentFilter) {
-        mContext.registerReceiver(mSettingChangeReceiver, intentFilter);
+        mContext.registerReceiver(mSettingChangeReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 44b83096..4fef2a8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -2225,8 +2225,7 @@
                 ProtoOutputStream proto = new ProtoOutputStream();
                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
                 long timeOffsetNs =
-                        TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(),
-                                                     TimeUnit.NANOSECONDS)
+                        TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
                         - SystemClock.elapsedRealtimeNanos();
                 proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
                 mBuffer.writeTraceToFile(mTraceFile, proto);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17a9a63..0eee8a3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -496,7 +496,7 @@
     /** The most recently given options. */
     private ActivityOptions mPendingOptions;
     /** Non-null if {@link #mPendingOptions} specifies the remote animation. */
-    private RemoteAnimationAdapter mPendingRemoteAnimation;
+    RemoteAnimationAdapter mPendingRemoteAnimation;
     private RemoteTransition mPendingRemoteTransition;
     ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
     AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -812,7 +812,6 @@
     StartingData mStartingData;
     WindowState mStartingWindow;
     StartingSurfaceController.StartingSurface mStartingSurface;
-    boolean startingDisplayed;
     boolean startingMoved;
 
     /** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
     /** Whether the input to this activity will be dropped during the current playing animation. */
     private boolean mIsInputDroppedForAnimation;
 
-    /**
-     * If it is non-null, it requires all activities who have the same starting data to be drawn
-     * to remove the starting window.
-     * TODO(b/189385912): Remove starting window related fields after migrating them to task.
-     */
-    private StartingData mSharedStartingData;
-
     boolean mHandleExitSplashScreen;
     @TransferSplashScreenState
     int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1201,14 +1193,11 @@
             pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
             pw.print(" mIsExiting="); pw.println(mIsExiting);
         }
-        if (mSharedStartingData != null) {
-            pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
-        }
-        if (mStartingWindow != null || mStartingSurface != null
-                || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+        if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+                || startingMoved || mVisibleSetFromTransferredStartingWindow) {
             pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
             pw.print(" startingSurface="); pw.print(mStartingSurface);
-            pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+            pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
             pw.print(" startingMoved="); pw.print(startingMoved);
             pw.println(" mVisibleSetFromTransferredStartingWindow="
                     + mVisibleSetFromTransferredStartingWindow);
@@ -2687,13 +2676,23 @@
         }
     }
 
+    boolean isStartingWindowDisplayed() {
+        final StartingData data = mStartingData != null ? mStartingData : task != null
+                ? task.mSharedStartingData : null;
+        return data != null && data.mIsDisplayed;
+    }
+
     /** Called when the starting window is added to this activity. */
     void attachStartingWindow(@NonNull WindowState startingWindow) {
         startingWindow.mStartingData = mStartingData;
         mStartingWindow = startingWindow;
-        // The snapshot type may have called associateStartingDataWithTask().
-        if (mStartingData != null && mStartingData.mAssociatedTask != null) {
-            attachStartingSurfaceToAssociatedTask();
+        if (mStartingData != null) {
+            if (mStartingData.mAssociatedTask != null) {
+                // The snapshot type may have called associateStartingDataWithTask().
+                attachStartingSurfaceToAssociatedTask();
+            } else if (isEmbedded()) {
+                associateStartingWindowWithTaskIfNeeded();
+            }
         }
     }
 
@@ -2708,11 +2707,7 @@
     /** Called when the starting window is not added yet but its data is known to fill the task. */
     private void associateStartingDataWithTask() {
         mStartingData.mAssociatedTask = task;
-        task.forAllActivities(r -> {
-            if (r.mVisibleRequested && !r.firstWindowDrawn) {
-                r.mSharedStartingData = mStartingData;
-            }
-        });
+        task.mSharedStartingData = mStartingData;
     }
 
     /** Associates and attaches an added starting window to the current task. */
@@ -2743,10 +2738,8 @@
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
         mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
-        if (mSharedStartingData != null) {
-            mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
-                r.mSharedStartingData = null;
-            });
+        if (task != null) {
+            task.mSharedStartingData = null;
         }
         if (mStartingWindow == null) {
             if (mStartingData != null) {
@@ -2774,7 +2767,6 @@
             mStartingData = null;
             mStartingSurface = null;
             mStartingWindow = null;
-            startingDisplayed = false;
             if (surface == null) {
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
                         + "startingSurface==null, couldn't remove");
@@ -4247,7 +4239,7 @@
      * @return {@code true} if starting window is in app's hierarchy.
      */
     boolean hasStartingWindow() {
-        if (startingDisplayed || mStartingData != null) {
+        if (mStartingData != null) {
             return true;
         }
         for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4345,10 +4337,7 @@
 
                 // Transfer the starting window over to the new token.
                 mStartingData = fromActivity.mStartingData;
-                mSharedStartingData = fromActivity.mSharedStartingData;
                 mStartingSurface = fromActivity.mStartingSurface;
-                startingDisplayed = fromActivity.startingDisplayed;
-                fromActivity.startingDisplayed = false;
                 mStartingWindow = tStartingWindow;
                 reportedVisible = fromActivity.reportedVisible;
                 fromActivity.mStartingData = null;
@@ -4414,7 +4403,6 @@
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                     "Moving pending starting from %s to %s", fromActivity, this);
             mStartingData = fromActivity.mStartingData;
-            mSharedStartingData = fromActivity.mSharedStartingData;
             fromActivity.mStartingData = null;
             fromActivity.startingMoved = true;
             scheduleAddStartingWindow();
@@ -6526,14 +6514,11 @@
         // Remove starting window directly if is in a pure task. Otherwise if it is associated with
         // a task (e.g. nested task fragment), then remove only if all visible windows in the task
         // are drawn.
-        final Task associatedTask =
-                mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+        final Task associatedTask = task.mSharedStartingData != null ? task : null;
         if (associatedTask == null) {
             removeStartingWindow();
-        } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
-                // Don't block starting window removal if an Activity can't be a starting window
-                // target.
-                && r.mSharedStartingData != null) == null) {
+        } else if (associatedTask.getActivity(
+                r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
             // The last drawn activity may not be the one that owns the starting window.
             final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
             if (r != null) {
@@ -6748,7 +6733,6 @@
         if (mLastTransactionSequence != mWmService.mTransactionSequence) {
             mLastTransactionSequence = mWmService.mTransactionSequence;
             mNumDrawnWindows = 0;
-            startingDisplayed = false;
 
             // There is the main base application window, even if it is exiting, wait for it
             mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6792,9 +6776,9 @@
                         isInterestingAndDrawn = true;
                     }
                 }
-            } else if (w.isDrawn()) {
+            } else if (mStartingData != null && w.isDrawn()) {
                 // The starting window for this container is drawn.
-                startingDisplayed = true;
+                mStartingData.mIsDisplayed = true;
             }
         }
 
@@ -7552,7 +7536,8 @@
 
         ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
                 + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
-                this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+                this, reportedVisible, okToDisplay(), okToAnimate(),
+                isStartingWindowDisplayed());
 
         // clean up thumbnail window
         if (mThumbnail != null) {
@@ -9650,7 +9635,7 @@
         if (mStartingWindow != null) {
             mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
         }
-        proto.write(STARTING_DISPLAYED, startingDisplayed);
+        proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
         proto.write(STARTING_MOVED, startingMoved);
         proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
                 mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1d70146..dc047aa 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2643,10 +2643,14 @@
             }
         }
 
-        // Update the target's launch cookie to those specified in the options if set
+        // Update the target's launch cookie and pending remote animation to those specified in the
+        // options if set.
         if (mStartActivity.mLaunchCookie != null) {
             intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
         }
+        if (mStartActivity.mPendingRemoteAnimation != null) {
+            intentActivity.mPendingRemoteAnimation = mStartActivity.mPendingRemoteAnimation;
+        }
 
         // Need to update mTargetRootTask because if task was moved out of it, the original root
         // task may be destroyed.
@@ -2728,7 +2732,12 @@
                 newParent = candidateTf;
             }
         }
-        newParent.mTransitionController.collect(newParent);
+        if (newParent.asTask() == null) {
+            // only collect task-fragments.
+            // TODO(b/258095975): we probably shouldn't ever collect the parent here since it isn't
+            //                    changing. The logic that changes it should collect it.
+            newParent.mTransitionController.collect(newParent);
+        }
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c2e87e6..9011533 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -76,7 +76,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.IntDef;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.os.Trace;
@@ -167,16 +166,6 @@
                 ? null : wallpaperTarget;
     }
 
-    @NonNull
-    private static ArraySet<ActivityRecord> getAppsForAnimation(
-            @NonNull ArraySet<ActivityRecord> apps, boolean excludeLauncherFromAnimation) {
-        final ArraySet<ActivityRecord> appsForAnimation = new ArraySet<>(apps);
-        if (excludeLauncherFromAnimation) {
-            appsForAnimation.removeIf(ConfigurationContainer::isActivityTypeHome);
-        }
-        return appsForAnimation;
-    }
-
     /**
      * Handle application transition for given display.
      */
@@ -226,45 +215,32 @@
         mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
                 mDisplayContent.mOpeningApps);
 
-        // Remove launcher from app transition animation while recents is running. Recents animation
-        // is managed outside of app transition framework, so we just need to commit visibility.
-        final boolean excludeLauncherFromAnimation =
-                mDisplayContent.mOpeningApps.stream().anyMatch(
-                        (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS))
-                || mDisplayContent.mClosingApps.stream().anyMatch(
-                        (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS));
-        final ArraySet<ActivityRecord> openingAppsForAnimation = getAppsForAnimation(
-                mDisplayContent.mOpeningApps, excludeLauncherFromAnimation);
-        final ArraySet<ActivityRecord> closingAppsForAnimation = getAppsForAnimation(
-                mDisplayContent.mClosingApps, excludeLauncherFromAnimation);
-
         @TransitionOldType final int transit = getTransitCompatType(
-                mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation,
-                mDisplayContent.mChangingContainers,
+                mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
+                mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
                 mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
                 mDisplayContent.mSkipAppTransitionAnimation);
         mDisplayContent.mSkipAppTransitionAnimation = false;
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "handleAppTransitionReady: displayId=%d appTransition={%s}"
-                + " excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
-                mDisplayContent.mDisplayId, appTransition.toString(), excludeLauncherFromAnimation,
-                mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
-                AppTransition.appTransitionOldToString(transit));
+                + " openingApps=[%s] closingApps=[%s] transit=%s",
+                mDisplayContent.mDisplayId, appTransition.toString(), mDisplayContent.mOpeningApps,
+                mDisplayContent.mClosingApps, AppTransition.appTransitionOldToString(transit));
 
         // Find the layout params of the top-most application window in the tokens, which is
         // what will control the animation theme. If all closing windows are obscured, then there is
         // no need to do an animation. This is the case, for example, when this transition is being
         // done behind a dream window.
-        final ArraySet<Integer> activityTypes = collectActivityTypes(openingAppsForAnimation,
-                closingAppsForAnimation, mDisplayContent.mChangingContainers);
+        final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
+                mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
         final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
-                openingAppsForAnimation, closingAppsForAnimation,
+                mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                 mDisplayContent.mChangingContainers);
         final ActivityRecord topOpeningApp =
-                getTopApp(openingAppsForAnimation, false /* ignoreHidden */);
+                getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
         final ActivityRecord topClosingApp =
-                getTopApp(closingAppsForAnimation, false /* ignoreHidden */);
+                getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
         final ActivityRecord topChangingApp =
                 getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
         final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
@@ -276,14 +252,14 @@
             overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
         }
 
-        final boolean voiceInteraction = containsVoiceInteraction(closingAppsForAnimation)
-                || containsVoiceInteraction(openingAppsForAnimation);
+        final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
+                || containsVoiceInteraction(mDisplayContent.mOpeningApps);
 
         final int layoutRedo;
         mService.mSurfaceAnimationRunner.deferStartingAnimations();
         try {
-            applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp,
-                    voiceInteraction);
+            applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
+                    animLp, voiceInteraction);
             handleClosingApps();
             handleOpeningApps();
             handleChangingApps(transit);
@@ -294,8 +270,8 @@
             final int flags = appTransition.getTransitFlags();
             layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
             appTransition.postAnimationCallback();
-            appTransition.clear();
         } finally {
+            appTransition.clear();
             mService.mSurfaceAnimationRunner.continueStartingAnimations();
         }
 
@@ -1200,14 +1176,19 @@
             if (activity == null) {
                 continue;
             }
+            if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+                        "Delaying app transition for recents animation to finish");
+                return false;
+            }
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
                             + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
-                    activity, activity.allDrawn, activity.startingDisplayed,
+                    activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
                     activity.startingMoved, activity.isRelaunching(),
                     activity.mStartingWindow);
             final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
-            if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+            if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
                 return false;
             }
             if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1cb83f12..6ff2bb3 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -24,7 +24,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -41,7 +40,6 @@
 import android.window.BackNavigationInfo;
 import android.window.IBackAnimationFinishedCallback;
 import android.window.OnBackInvokedCallbackInfo;
-import android.window.ScreenCapture;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
@@ -243,16 +241,22 @@
             } else if (currentActivity.isRootOfTask()) {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
-                // TODO: Also check Task.shouldUpRecreateTaskLocked() for prevActivity logic
                 prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
                 removedWindowContainer = currentTask;
-                prevActivity = prevTask.getTopNonFinishingActivity();
-                if (prevTask.isActivityTypeHome()) {
-                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                // If it reaches the top activity, we will check the below task from parent.
+                // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
+                // or set the type to proper value when it's return to home or another task.
+                if (prevTask == null || prevTask.inMultiWindowMode()) {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
                 } else {
-                    backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    prevActivity = prevTask.getTopNonFinishingActivity();
+                    if (prevTask.isActivityTypeHome()) {
+                        backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                        mShowWallpaper = true;
+                    } else {
+                        backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    }
                 }
-                mShowWallpaper = true;
             }
             infoBuilder.setType(backType);
 
@@ -263,8 +267,11 @@
                     removedWindowContainer,
                     BackNavigationInfo.typeToString(backType));
 
-            // For now, we only animate when going home.
-            boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+            // For now, we only animate when going home, cross task or cross-activity.
+            boolean prepareAnimation =
+                    (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+                            || backType == BackNavigationInfo.TYPE_CROSS_TASK
+                            || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY)
                     && adapter != null;
 
             // Only prepare animation if no leash has been created (no animation is running).
@@ -328,6 +335,7 @@
         RemoteAnimationTarget behindAppTarget = null;
         if (needsScreenshot(backType)) {
             HardwareBuffer screenshotBuffer = null;
+            Task backTargetTask = prevTask;
             switch(backType) {
                 case BackNavigationInfo.TYPE_CROSS_TASK:
                     int prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
@@ -335,14 +343,10 @@
                     screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
                     break;
                 case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
-                    //TODO(207481538) Remove once the infrastructure to support per-activity
-                    // screenshot is implemented. For now we simply have the mBackScreenshots hash
-                    // map that dumbly saves the screenshots.
-                    if (prevActivity != null
-                            && prevActivity.mActivityComponent != null) {
-                        screenshotBuffer =
-                                getActivitySnapshot(currentTask, prevActivity.mActivityComponent);
+                    if (prevActivity != null && prevActivity.mActivityComponent != null) {
+                        screenshotBuffer = getActivitySnapshot(currentTask, prevActivity);
                     }
+                    backTargetTask = currentTask;
                     break;
             }
 
@@ -361,8 +365,9 @@
                 // leash needs to be added before to be in the synchronized block.
                 startedTransaction.setLayer(topAppTarget.leash, 1);
 
-                behindAppTarget = createRemoteAnimationTargetLocked(
-                        prevTask, screenshotSurface, MODE_OPENING);
+                behindAppTarget =
+                        createRemoteAnimationTargetLocked(
+                                backTargetTask, screenshotSurface, MODE_OPENING);
 
                 // reset leash after animation finished.
                 leashes.add(screenshotSurface);
@@ -535,14 +540,8 @@
         mShowWallpaper = false;
     }
 
-    private HardwareBuffer getActivitySnapshot(@NonNull Task task,
-            ComponentName activityComponent) {
-        // Check if we have a screenshot of the previous activity, indexed by its
-        // component name.
-        ScreenCapture.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots
-                .get(activityComponent.flattenToString());
-        return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
-
+    private HardwareBuffer getActivitySnapshot(@NonNull Task task, ActivityRecord r) {
+        return task.getSnapshotForActivityRecord(r);
     }
 
     private HardwareBuffer getTaskSnapshot(int taskId, int userId) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 38931f2..7bcea36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -115,11 +115,9 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowLayout;
@@ -314,7 +312,6 @@
     private int mLastAppearance;
     private int mLastBehavior;
     private int mLastRequestedVisibleTypes = Type.defaultVisible();
-    private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
     private LetterboxDetails[] mLastLetterboxDetails;
 
@@ -2158,35 +2155,16 @@
             mService.mInputManager.setSystemUiLightsOut(
                     isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
         }
-        final InsetsVisibilities requestedVisibilities =
-                mLastRequestedVisibleTypes == requestedVisibleTypes
-                        ? mRequestedVisibilities
-                        : toInsetsVisibilities(requestedVisibleTypes);
         mLastAppearance = appearance;
         mLastBehavior = behavior;
         mLastRequestedVisibleTypes = requestedVisibleTypes;
-        mRequestedVisibilities = requestedVisibilities;
         mFocusedApp = focusedApp;
         mLastFocusIsFullscreen = isFullscreen;
         mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
         mLastLetterboxDetails = letterboxDetails;
         callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
                 appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
-                requestedVisibilities, focusedApp, letterboxDetails));
-    }
-
-    // TODO (253420890): Remove this when removing mRequestedVisibilities.
-    private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
-        final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
-        final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
-        for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
-            @InsetsType int type = InsetsState.toPublicType(i);
-            if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
-                // We only set the visibility if it is different from the default one.
-                insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
-            }
-        }
-        return insetsVisibilities;
+                requestedVisibleTypes, focusedApp, letterboxDetails));
     }
 
     private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7e56dbf..b9fa80c 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -26,6 +26,7 @@
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
@@ -288,8 +289,9 @@
         final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
         // Always use windowing mode fullscreen when get insets for window metrics to make sure it
         // contains all insets types.
-        final InsetsState originalState = enforceInsetsPolicyForTarget(WINDOWING_MODE_FULLSCREEN,
-                alwaysOnTop, attrs, mStateController.getRawInsetsState());
+        final InsetsState originalState = mDisplayContent.getInsetsPolicy()
+                .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
+                        attrs.type, mStateController.getRawInsetsState());
         InsetsState state = adjustVisibilityForTransientTypes(originalState);
         return adjustInsetsForRoundedCorners(token, state, state == originalState);
     }
@@ -341,42 +343,56 @@
 
 
     /**
-     * Modifies the given {@code state} according to the target's window state.
-     * When performing layout of the target or dispatching insets to the target, we need to adjust
-     * sources based on the target. e.g., the floating window will not receive system bars other
-     * than caption, and some insets provider may request to override sizes for given window types.
-     * Since the window type and the insets types provided by the window shall not change at
-     * runtime, rotation doesn't matter in the layout params.
+     * Modifies the given {@code state} according to the {@code type} (Inset type) provided by
+     * the target.
+     * When performing layout of the target or dispatching insets to the target, we need to exclude
+     * sources which should not be visible to the target. e.g., the source which represents the
+     * target window itself, and the IME source when the target is above IME. We also need to
+     * exclude certain types of insets source for client within specific windowing modes.
      *
+     * @param type the inset type provided by the target
      * @param windowingMode the windowing mode of the target
      * @param isAlwaysOnTop is the target always on top
-     * @param attrs the layout params of the target
+     * @param windowType the type of the target
      * @param state the input inset state containing all the sources
      * @return The state stripped of the necessary information.
      */
-    InsetsState enforceInsetsPolicyForTarget(@WindowConfiguration.WindowingMode int windowingMode,
-            boolean isAlwaysOnTop, WindowManager.LayoutParams attrs, InsetsState state) {
+    InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
+            @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
+            int windowType, InsetsState state) {
         boolean stateCopied = false;
 
-        if (attrs.providedInsets != null && attrs.providedInsets.length > 0) {
+        if (type != ITYPE_INVALID) {
             state = new InsetsState(state);
             stateCopied = true;
-            for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
-                state.removeSource(attrs.providedInsets[i].type);
+            state.removeSource(type);
+
+            // Navigation bar doesn't get influenced by anything else
+            if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
+                state.removeSource(ITYPE_STATUS_BAR);
+                state.removeSource(ITYPE_CLIMATE_BAR);
+                state.removeSource(ITYPE_CAPTION_BAR);
+                state.removeSource(ITYPE_NAVIGATION_BAR);
+                state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
+            }
+
+            // Status bar doesn't get influenced by caption bar
+            if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
+                state.removeSource(ITYPE_CAPTION_BAR);
             }
         }
         ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
                 .getSourceProviders();
         for (int i = providers.size() - 1; i >= 0; i--) {
             WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
-            if (otherProvider.overridesFrame(attrs.type)) {
+            if (otherProvider.overridesFrame(windowType)) {
                 if (!stateCopied) {
                     state = new InsetsState(state);
                     stateCopied = true;
                 }
                 InsetsSource override =
                         new InsetsSource(state.getSource(otherProvider.getSource().getType()));
-                override.setFrame(otherProvider.getOverriddenFrame(attrs.type));
+                override.setFrame(otherProvider.getOverriddenFrame(windowType));
                 state.addSource(override);
             }
         }
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 9c85bc0..1cc1a57 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -20,13 +20,12 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.function.Consumer;
 
 /**
@@ -39,15 +38,13 @@
     static final int FLAG_CROSS_USERS = 1 << 2;
     static final int FLAG_KEEP_INTENT_EXTRA = 1 << 3;
 
-    // Comparator to sort by last active time (descending)
-    private static final Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR =
-            (o1, o2) -> {
-                return o1.lastActiveTime == o2.lastActiveTime
-                        ? Integer.signum(o2.mTaskId - o1.mTaskId) :
-                        Long.signum(o2.lastActiveTime - o1.lastActiveTime);
-            };
-
-    private final TreeSet<Task> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+    // Tasks are sorted in order {focusedVisibleTasks, visibleTasks, invisibleTasks}.
+    private final ArrayList<Task> mTmpSortedTasks = new ArrayList<>();
+    // mTmpVisibleTasks, mTmpInvisibleTasks and mTmpFocusedTasks are sorted from top
+    // to bottom.
+    private final ArrayList<Task> mTmpVisibleTasks = new ArrayList<>();
+    private final ArrayList<Task> mTmpInvisibleTasks = new ArrayList<>();
+    private final ArrayList<Task> mTmpFocusedTasks = new ArrayList<>();
 
     private int mCallingUid;
     private int mUserId;
@@ -65,8 +62,6 @@
             return;
         }
 
-        // Gather all of the tasks across all of the tasks, and add them to the sorted set
-        mTmpSortedSet.clear();
         mCallingUid = callingUid;
         mUserId = UserHandle.getUserId(callingUid);
         mCrossUser = (flags & FLAG_CROSS_USERS) == FLAG_CROSS_USERS;
@@ -77,19 +72,64 @@
         mRecentTasks = recentTasks;
         mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
 
-        root.forAllLeafTasks(this, false /* traverseTopToBottom */);
+        if (root instanceof RootWindowContainer) {
+            ((RootWindowContainer) root).forAllDisplays(dc -> {
+                final Task focusedTask = dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null;
+                if (focusedTask != null) {
+                    mTmpFocusedTasks.add(focusedTask);
+                }
+                processTaskInWindowContainer(dc);
+            });
+        } else {
+            final DisplayContent dc = root.getDisplayContent();
+            final Task focusedTask = dc != null
+                    ? (dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null)
+                    : null;
+            // May not be include focusedTask if root is DisplayArea.
+            final boolean rootContainsFocusedTask = focusedTask != null
+                    && focusedTask.isDescendantOf(root);
+            if (rootContainsFocusedTask) {
+                mTmpFocusedTasks.add(focusedTask);
+            }
+            processTaskInWindowContainer(root);
+        }
+
+        final int visibleTaskCount = mTmpVisibleTasks.size();
+        for (int i = 0; i < mTmpFocusedTasks.size(); i++) {
+            final Task focusedTask = mTmpFocusedTasks.get(i);
+            final boolean containsFocusedTask = mTmpVisibleTasks.remove(focusedTask);
+            if (containsFocusedTask) {
+                // Put the visible focused task at the first position.
+                mTmpSortedTasks.add(focusedTask);
+            }
+        }
+        if (!mTmpVisibleTasks.isEmpty()) {
+            mTmpSortedTasks.addAll(mTmpVisibleTasks);
+        }
+        if (!mTmpInvisibleTasks.isEmpty()) {
+            mTmpSortedTasks.addAll(mTmpInvisibleTasks);
+        }
 
         // Take the first {@param maxNum} tasks and create running task infos for them
-        final Iterator<Task> iter = mTmpSortedSet.iterator();
-        while (iter.hasNext()) {
-            if (maxNum == 0) {
-                break;
-            }
-
-            final Task task = iter.next();
-            list.add(createRunningTaskInfo(task));
-            maxNum--;
+        final int size = Math.min(maxNum, mTmpSortedTasks.size());
+        final long now = SystemClock.elapsedRealtime();
+        for (int i = 0; i < size; i++) {
+            final Task task = mTmpSortedTasks.get(i);
+            // Override the last active to current time for the visible tasks because the visible
+            // tasks can be considered to be currently active, the values are descending as
+            // the item order.
+            final long visibleActiveTime = i < visibleTaskCount ? now + size - i : -1;
+            list.add(createRunningTaskInfo(task, visibleActiveTime));
         }
+
+        mTmpFocusedTasks.clear();
+        mTmpVisibleTasks.clear();
+        mTmpInvisibleTasks.clear();
+        mTmpSortedTasks.clear();
+    }
+
+    private void processTaskInWindowContainer(WindowContainer wc) {
+        wc.forAllLeafTasks(this, true /* traverseTopToBottom */);
     }
 
     @Override
@@ -117,25 +157,20 @@
             // home & recent tasks
             return;
         }
-
         if (task.isVisible()) {
-            // For the visible task, update the last active time so that it can be used to determine
-            // the order of the tasks (it may not be set for newly created tasks)
-            task.touchActiveTime();
-            if (!task.isFocused()) {
-                // TreeSet doesn't allow the same value and make sure this task is lower than the
-                // focused one.
-                task.lastActiveTime -= mTmpSortedSet.size();
-            }
+            mTmpVisibleTasks.add(task);
+        } else {
+            mTmpInvisibleTasks.add(task);
         }
-
-        mTmpSortedSet.add(task);
     }
 
     /** Constructs a {@link RunningTaskInfo} from a given {@param task}. */
-    private RunningTaskInfo createRunningTaskInfo(Task task) {
+    private RunningTaskInfo createRunningTaskInfo(Task task, long visibleActiveTime) {
         final RunningTaskInfo rti = new RunningTaskInfo();
         task.fillTaskInfo(rti, !mKeepIntentExtra);
+        if (visibleActiveTime > 0) {
+            rti.lastActiveTime = visibleActiveTime;
+        }
         // Fill in some deprecated values
         rti.id = rti.taskId;
 
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
      */
     Task mAssociatedTask;
 
+    /** Whether the starting window is drawn. */
+    boolean mIsDisplayed;
+
     protected StartingData(WindowManagerService service, int typeParams) {
         mService = service;
         mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 435ab97..d06f271 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
 
     int mLockTaskUid = -1;  // The uid of the application that called startLockTask().
 
+    /**
+     * If non-null, the starting window should cover the associated task. It is assigned when the
+     * parent activity of starting window is put in a partial area of the task. This field will be
+     * cleared when all visible activities in this task are drawn.
+     */
+    StartingData mSharedStartingData;
+
     /** The process that had previously hosted the root activity of this task.
      * Used to know that we should try harder to keep this process around, in case the
      * user wants to return to it. */
@@ -3673,6 +3680,9 @@
         if (mRootProcess != null) {
             pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
         }
+        if (mSharedStartingData != null) {
+            pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+        }
         pw.print(prefix); pw.print("taskId=" + mTaskId);
         pw.println(" rootTaskId=" + getRootTaskId());
         pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 230b760..872542a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -79,6 +79,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -1859,7 +1860,6 @@
         super.addChild(child, index);
 
         if (isAddingActivity && task != null) {
-
             // TODO(b/207481538): temporary per-activity screenshoting
             if (r != null && BackNavigationController.isScreenshotEnabled()) {
                 ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
@@ -1891,10 +1891,10 @@
     RemoteAnimationTarget createRemoteAnimationTarget(
             RemoteAnimationController.RemoteAnimationRecord record) {
         final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
-                // There may be a trampoline activity without window on top of the existing task
-                // which is moving to front. Exclude the finishing activity so the window of next
-                // activity can be chosen to create the animation target.
-                ? getTopNonFinishingActivity()
+                // There may be a launching (e.g. trampoline or embedded) activity without a window
+                // on top of the existing task which is moving to front. Exclude finishing activity
+                // so the window of next activity can be chosen to create the animation target.
+                ? getActivity(r -> !r.finishing && r.hasChild())
                 : getTopMostActivity();
         return activity != null ? activity.createRemoteAnimationTarget(record) : null;
     }
@@ -2327,6 +2327,11 @@
         if (mTaskFragmentOrganizer != null
                 && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
             t.setWindowCrop(mSurfaceControl, 0, 0);
+            final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
+            if (t != syncTransaction) {
+                // Avoid restoring to old window crop if the sync transaction is applied later.
+                syncTransaction.setWindowCrop(mSurfaceControl, 0, 0);
+            }
             mLastSurfaceSize.set(0, 0);
         }
     }
@@ -2528,6 +2533,19 @@
         return !mCreatedByOrganizer || mIsRemovalRequested;
     }
 
+    @Nullable
+    HardwareBuffer getSnapshotForActivityRecord(@Nullable ActivityRecord r) {
+        if (!BackNavigationController.isScreenshotEnabled()) {
+            return null;
+        }
+        if (r != null && r.mActivityComponent != null) {
+            ScreenCapture.ScreenshotHardwareBuffer backBuffer =
+                    mBackScreenshots.get(r.mActivityComponent.flattenToString());
+            return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
+        }
+        return null;
+    }
+
     @Override
     void removeChild(WindowContainer child) {
         removeChild(child, true /* removeSelfIfPossible */);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 73d4496..5087a0b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3277,9 +3277,10 @@
 
     void resetSurfacePositionForAnimationLeash(Transaction t) {
         t.setPosition(mSurfaceControl, 0, 0);
-        if (mSyncState != SYNC_STATE_NONE && t != mSyncTransaction) {
+        final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
+        if (t != syncTransaction) {
             // Avoid restoring to old position if the sync transaction is applied later.
-            mSyncTransaction.setPosition(mSurfaceControl, 0, 0);
+            syncTransaction.setPosition(mSurfaceControl, 0, 0);
         }
         mLastSurfacePosition.set(0, 0);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 848c231..edb80d7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5295,7 +5295,6 @@
         public static final int WINDOW_FREEZE_TIMEOUT = 11;
 
         public static final int PERSIST_ANIMATION_SCALE = 14;
-        public static final int FORCE_GC = 15;
         public static final int ENABLE_SCREEN = 16;
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int REPORT_WINDOWS_CHANGE = 19;
@@ -5386,26 +5385,6 @@
                     break;
                 }
 
-                case FORCE_GC: {
-                    synchronized (mGlobalLock) {
-                        // Since we're holding both mWindowMap and mAnimator we don't need to
-                        // hold mAnimator.mLayoutToAnim.
-                        if (mAnimator.isAnimationScheduled()) {
-                            // If we are animating, don't do the gc now but
-                            // delay a bit so we don't interrupt the animation.
-                            sendEmptyMessageDelayed(H.FORCE_GC, 2000);
-                            return;
-                        }
-                        // If we are currently rotating the display, it will
-                        // schedule a new message when done.
-                        if (mDisplayFrozen) {
-                            return;
-                        }
-                    }
-                    Runtime.getRuntime().gc();
-                    break;
-                }
-
                 case ENABLE_SCREEN: {
                     performEnableScreen();
                     break;
@@ -6264,14 +6243,6 @@
         // now to catch that.
         configChanged = displayContent != null && displayContent.updateOrientation();
 
-        // A little kludge: a lot could have happened while the
-        // display was frozen, so now that we are coming back we
-        // do a gc so that any remote references the system
-        // processes holds on others can be released if they are
-        // no longer needed.
-        mH.removeMessages(H.FORCE_GC);
-        mH.sendEmptyMessageDelayed(H.FORCE_GC, 2000);
-
         mScreenFrozenLock.release();
 
         if (updateRotation && displayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3590e9c2..7415376 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1547,6 +1547,12 @@
         return mTransitionController.mTransitionMetricsReporter;
     }
 
+    @Override
+    public IBinder getApplyToken() {
+        enforceTaskPermission("getApplyToken()");
+        return SurfaceControl.Transaction.getDefaultApplyToken();
+    }
+
     /** Whether the configuration changes are important to report back to an organizer. */
     static boolean configurationsAreEqualForOrganizer(
             Configuration newConfig, @Nullable Configuration oldConfig) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f30c435..fa843f2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -25,10 +25,12 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.graphics.GraphicsProtos.dumpPointProto;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -201,6 +203,7 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.gui.TouchOcclusionMode;
+import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Debug;
@@ -1672,11 +1675,14 @@
         if (rotatedState != null) {
             return insetsPolicy.adjustInsetsForWindow(this, rotatedState);
         }
+        final InsetsSourceProvider provider = getControllableInsetProvider();
+        final @InternalInsetsType int insetTypeProvidedByWindow = provider != null
+                ? provider.getSource().getType() : ITYPE_INVALID;
         final InsetsState rawInsetsState =
                 mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
         final InsetsState insetsStateForWindow = insetsPolicy
-                .enforceInsetsPolicyForTarget(
-                        getWindowingMode(), isAlwaysOnTop(), mAttrs, rawInsetsState);
+                .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
+                        getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState);
         return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
                 includeTransient);
     }
@@ -1838,8 +1844,8 @@
      * @return {@code true} if one or more windows have been displayed, else false.
      */
     boolean hasAppShownWindows() {
-        return mActivityRecord != null
-                && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+        return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+                || mActivityRecord.isStartingWindowDisplayed());
     }
 
     @Override
@@ -5475,8 +5481,10 @@
         // If refresh rate switching is disabled there is no point to set the frame rate on the
         // surface as the refresh rate will be limited by display manager to a single value
         // and SurfaceFlinger wouldn't be able to change it anyways.
-        if (mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType()
-                != SWITCHING_TYPE_NONE) {
+        @DisplayManager.SwitchingType int refreshRateSwitchingType =
+                mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
+        if (refreshRateSwitchingType != SWITCHING_TYPE_NONE
+                && refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
             final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
             if (mAppPreferredFrameRate != refreshRate) {
                 mAppPreferredFrameRate = refreshRate;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6e16b5d..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
     }
 
     void destroySurfaceLocked(SurfaceControl.Transaction t) {
-        final ActivityRecord activity = mWin.mActivityRecord;
-        if (activity != null) {
-            if (mWin == activity.mStartingWindow) {
-                activity.startingDisplayed = false;
-            }
-        }
-
         if (mSurfaceController == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index efcca5d..416d042 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -355,7 +355,7 @@
             ProtoOutputStream proto = new ProtoOutputStream();
             proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
             long timeOffsetNs =
-                    TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+                    TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
                     - SystemClock.elapsedRealtimeNanos();
             proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
             mBuffer.writeTraceToFile(mTraceFile, proto);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 5fa8dcc..3c78819 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -152,7 +152,7 @@
         "android.hardware.broadcastradio@1.0",
         "android.hardware.broadcastradio@1.1",
         "android.hardware.contexthub@1.0",
-        "android.hardware.gnss-V2-cpp",
+        "android.hardware.gnss-V3-cpp",
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index 0531ae2..f3ba484f62 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -61,7 +61,7 @@
         "libnativehelper",
         "libhardware_legacy",
         "libutils",
-        "android.hardware.gnss-V2-cpp",
+        "android.hardware.gnss-V3-cpp",
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
diff --git a/services/core/jni/gnss/GnssCallback.cpp b/services/core/jni/gnss/GnssCallback.cpp
index b931e91..3c1ac1e 100644
--- a/services/core/jni/gnss/GnssCallback.cpp
+++ b/services/core/jni/gnss/GnssCallback.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "GnssCallbckJni"
+#define LOG_TAG "GnssCallbackJni"
 
 #include "GnssCallback.h"
 
@@ -31,6 +31,7 @@
 using hardware::Void;
 
 using GnssLocationAidl = android::hardware::gnss::GnssLocation;
+using GnssSignalType = android::hardware::gnss::GnssSignalType;
 using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
 using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
 using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
@@ -42,11 +43,18 @@
 
 namespace {
 
+jclass class_arrayList;
+jclass class_gnssSignalType;
+
+jmethodID method_arrayListAdd;
+jmethodID method_arrayListCtor;
+jmethodID method_gnssSignalTypeCreate;
 jmethodID method_reportLocation;
 jmethodID method_reportStatus;
 jmethodID method_reportSvStatus;
 jmethodID method_reportNmea;
 jmethodID method_setTopHalCapabilities;
+jmethodID method_setSignalTypeCapabilities;
 jmethodID method_setGnssYearOfHardware;
 jmethodID method_setGnssHardwareModelName;
 jmethodID method_requestLocation;
@@ -88,6 +96,8 @@
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
 
     method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
+    method_setSignalTypeCapabilities =
+            env->GetMethodID(clazz, "setSignalTypeCapabilities", "(Ljava/util/List;)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
     method_setGnssHardwareModelName =
             env->GetMethodID(clazz, "setGnssHardwareModelName", "(Ljava/lang/String;)V");
@@ -95,16 +105,58 @@
     method_requestLocation = env->GetMethodID(clazz, "requestLocation", "(ZZ)V");
     method_requestUtcTime = env->GetMethodID(clazz, "requestUtcTime", "()V");
     method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V");
+
+    jclass arrayListClass = env->FindClass("java/util/ArrayList");
+    class_arrayList = (jclass)env->NewGlobalRef(arrayListClass);
+    method_arrayListCtor = env->GetMethodID(class_arrayList, "<init>", "()V");
+    method_arrayListAdd = env->GetMethodID(class_arrayList, "add", "(Ljava/lang/Object;)Z");
+
+    jclass gnssSignalTypeClass = env->FindClass("android/location/GnssSignalType");
+    class_gnssSignalType = (jclass)env->NewGlobalRef(gnssSignalTypeClass);
+    method_gnssSignalTypeCreate =
+            env->GetStaticMethodID(class_gnssSignalType, "create",
+                                   "(IDLjava/lang/String;)Landroid/location/GnssSignalType;");
 }
 
 Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) {
-    ALOGD("GnssCallbackAidl::%s: %du\n", __func__, capabilities);
+    ALOGD("%s: %du\n", __func__, capabilities);
     JNIEnv* env = getJniEnv();
     env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return Status::ok();
 }
 
+namespace {
+
+jobject translateSingleSignalType(JNIEnv* env, const GnssSignalType& signalType) {
+    jstring jstringCodeType = env->NewStringUTF(signalType.codeType.c_str());
+    jobject signalTypeObject =
+            env->CallStaticObjectMethod(class_gnssSignalType, method_gnssSignalTypeCreate,
+                                        signalType.constellation, signalType.carrierFrequencyHz,
+                                        jstringCodeType);
+    env->DeleteLocalRef(jstringCodeType);
+    return signalTypeObject;
+}
+
+} // anonymous namespace
+
+Status GnssCallbackAidl::gnssSetSignalTypeCapabilitiesCb(
+        const std::vector<GnssSignalType>& signalTypes) {
+    ALOGD("%s: %d signal types", __func__, (int)signalTypes.size());
+    JNIEnv* env = getJniEnv();
+    jobject arrayList = env->NewObject(class_arrayList, method_arrayListCtor);
+    for (auto& signalType : signalTypes) {
+        jobject signalTypeObject = translateSingleSignalType(env, signalType);
+        env->CallBooleanMethod(arrayList, method_arrayListAdd, signalTypeObject);
+        // Delete Local Refs
+        env->DeleteLocalRef(signalTypeObject);
+    }
+    env->CallVoidMethod(mCallbacksObj, method_setSignalTypeCapabilities, arrayList);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(arrayList);
+    return Status::ok();
+}
+
 Status GnssCallbackAidl::gnssStatusCb(const GnssStatusValue status) {
     JNIEnv* env = getJniEnv();
     env->CallVoidMethod(mCallbacksObj, method_reportStatus, status);
diff --git a/services/core/jni/gnss/GnssCallback.h b/services/core/jni/gnss/GnssCallback.h
index a7f96fb..33acec8 100644
--- a/services/core/jni/gnss/GnssCallback.h
+++ b/services/core/jni/gnss/GnssCallback.h
@@ -61,6 +61,8 @@
 class GnssCallbackAidl : public hardware::gnss::BnGnssCallback {
 public:
     binder::Status gnssSetCapabilitiesCb(const int capabilities) override;
+    binder::Status gnssSetSignalTypeCapabilitiesCb(
+            const std::vector<android::hardware::gnss::GnssSignalType>& signalTypes) override;
     binder::Status gnssStatusCb(const GnssStatusValue status) override;
     binder::Status gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) override;
     binder::Status gnssLocationCb(const hardware::gnss::GnssLocation& location) override;
@@ -180,4 +182,4 @@
 
 } // namespace android::gnss
 
-#endif // _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
\ No newline at end of file
+#endif // _ANDROID_SERVER_GNSS_GNSSCALLBACK_H
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 40412db..321f022 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
@@ -155,5 +156,14 @@
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
             return cancelTransport;
         }
+
+        @Override
+        public ICancellationSignal clearCredentialSession(
+                IClearCredentialSessionCallback callback, String callingPackage) {
+            // TODO: implement.
+            Log.i(TAG, "clearCredentialSession");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
+        }
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 70422bb..26f0251 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13982,7 +13982,6 @@
     @Override
     public boolean isProvisioningAllowed(String action, String packageName) {
         Objects.requireNonNull(packageName);
-
         final CallerIdentity caller = getCallerIdentity();
         final long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -13994,21 +13993,21 @@
             mInjector.binderRestoreCallingIdentity(ident);
         }
 
-        return checkProvisioningPreconditionSkipPermission(action, packageName) == STATUS_OK;
+        return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId())
+                == STATUS_OK;
     }
 
     @Override
     public int checkProvisioningPrecondition(String action, String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
-
+        final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
-        return checkProvisioningPreconditionSkipPermission(action, packageName);
+        return checkProvisioningPreconditionSkipPermission(action, packageName, caller.getUserId());
     }
-
     private int checkProvisioningPreconditionSkipPermission(String action,
-            String packageName) {
+            String packageName, int userId) {
         if (!mHasFeature) {
             logMissingFeatureAction("Cannot check provisioning for action " + action);
             return STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
@@ -14016,7 +14015,8 @@
         if (!isProvisioningAllowed()) {
             return STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
         }
-        final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName);
+        final int code = checkProvisioningPreConditionSkipPermissionNoLog(
+                action, packageName, userId);
         if (code != STATUS_OK) {
             Slogf.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName
                     + ") failed: "
@@ -14043,15 +14043,14 @@
     }
 
     private int checkProvisioningPreConditionSkipPermissionNoLog(String action,
-            String packageName) {
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
+            String packageName, int userId) {
         if (action != null) {
             switch (action) {
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
-                    return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
+                    return checkManagedProfileProvisioningPreCondition(packageName, userId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
                 case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
-                    return checkDeviceOwnerProvisioningPreCondition(callingUserId);
+                    return checkDeviceOwnerProvisioningPreCondition(userId);
             }
         }
         throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -17622,7 +17621,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             final int result = checkProvisioningPreconditionSkipPermission(
-                    ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName());
+                    ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName(), caller.getUserId());
             if (result != STATUS_OK) {
                 throw new ServiceSpecificException(
                         ERROR_PRE_CONDITION_FAILED,
@@ -18032,7 +18031,8 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             int result = checkProvisioningPreconditionSkipPermission(
-                    ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
+                    ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName(),
+                    caller.getUserId());
             if (result != STATUS_OK) {
                 throw new ServiceSpecificException(
                         ERROR_PRE_CONDITION_FAILED,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 593e648..88c0ec6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -318,8 +318,6 @@
             "com.android.clockwork.displayoffload.DisplayOffloadService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.android.clockwork.display.WearDisplayService";
-    private static final String WEAR_LEFTY_SERVICE_CLASS =
-            "com.google.android.clockwork.lefty.WearLeftyService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.android.clockwork.time.WearTimeService";
     private static final String WEAR_GLOBAL_ACTIONS_SERVICE_CLASS =
@@ -1404,7 +1402,6 @@
                 false);
         boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
                 false);
-        boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false);
 
         boolean isEmulator = SystemProperties.get("ro.boot.qemu").equals("1");
 
@@ -2502,12 +2499,6 @@
             mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
             t.traceEnd();
 
-            if (enableLeftyService) {
-                t.traceBegin("StartWearLeftyService");
-                mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
-                t.traceEnd();
-            }
-
             t.traceBegin("StartWearGlobalActionsService");
             mSystemServiceManager.startService(WEAR_GLOBAL_ACTIONS_SERVICE_CLASS);
             t.traceEnd();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 5b7b8f4..0f7c0d7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -16,6 +16,13 @@
 
 package com.android.server.am;
 
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ALARM;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_FOREGROUND;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_INTERACTIVE;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_MANIFEST;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ORDERED;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED;
+import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_RESULT_TO;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
 import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN;
@@ -25,6 +32,7 @@
 import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
 import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
 import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+import static com.android.server.am.BroadcastQueueTest.withPriority;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,19 +43,24 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 
 import androidx.test.filters.SmallTest;
 
@@ -59,6 +72,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
@@ -135,6 +150,18 @@
         }
     }
 
+    private static Intent makeMockIntent() {
+        return mock(Intent.class);
+    }
+
+    private static ResolveInfo makeMockManifestReceiver() {
+        return mock(ResolveInfo.class);
+    }
+
+    private static BroadcastFilter makeMockRegisteredReceiver() {
+        return mock(BroadcastFilter.class);
+    }
+
     private BroadcastRecord makeBroadcastRecord(Intent intent) {
         return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
@@ -145,6 +172,10 @@
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
     }
 
+    private BroadcastRecord makeBroadcastRecord(Intent intent, List receivers) {
+        return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), receivers, false);
+    }
+
     private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options) {
         return makeBroadcastRecord(intent, options,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
@@ -152,8 +183,13 @@
 
     private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
             List receivers, boolean ordered) {
+        return makeBroadcastRecord(intent, options, receivers, null, ordered);
+    }
+
+    private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
+            List receivers, IIntentReceiver resultTo, boolean ordered) {
         return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
-                null, null, null, AppOpsManager.OP_NONE, options, receivers, null, null,
+                null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
                 Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
                 false, null, false, null);
     }
@@ -282,8 +318,9 @@
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver()));
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         queue.setProcessCached(false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -305,12 +342,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -339,9 +376,9 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
-                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
-                        makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+                List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                        withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -362,8 +399,9 @@
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
+                List.of(makeMockRegisteredReceiver()));
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -377,6 +415,129 @@
     }
 
     /**
+     * Verify that a cached process that would normally be delayed becomes
+     * immediately runnable when the given broadcast is enqueued.
+     */
+    private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        queue.setProcessCached(true);
+
+        final BroadcastRecord lazyRecord = makeBroadcastRecord(
+                new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+                List.of(makeMockRegisteredReceiver()));
+
+        queue.enqueueOrReplaceBroadcast(lazyRecord, 0);
+        assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
+        assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
+
+        queue.enqueueOrReplaceBroadcast(testRecord, 0);
+        assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
+        assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Manifest() {
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                List.of(makeMockManifestReceiver()), null, false), REASON_CONTAINS_MANIFEST);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Ordered() {
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                List.of(makeMockRegisteredReceiver()), null, true), REASON_CONTAINS_ORDERED);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_ResultTo() {
+        final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                List.of(makeMockRegisteredReceiver()), resultTo, false), REASON_CONTAINS_RESULT_TO);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Foreground() {
+        final Intent foregroundIntent = new Intent();
+        foregroundIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        doRunnableAt_Cached(makeBroadcastRecord(foregroundIntent, null,
+                List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_FOREGROUND);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Interactive() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setInteractiveBroadcast(true);
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+                List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Alarm() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setAlarmBroadcast(true);
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+                List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_ALARM);
+    }
+
+    @Test
+    public void testRunnableAt_Cached_Prioritized() {
+        final List receivers = List.of(
+                withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+        doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+                receivers, null, false), REASON_CONTAINS_PRIORITIZED);
+    }
+
+    /**
+     * Confirm that we always prefer running pending items marked as "urgent",
+     * then "normal", then "offload", dispatching by the relative ordering
+     * within each of those clustering groups.
+     */
+    @Test
+    public void testMakeActiveNextPending() {
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
+
+        // To maximize test coverage, dump current state; we're not worried
+        // about the actual output, just that we don't crash
+        queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED);
+        queue.dumpLocked(SystemClock.uptimeMillis(),
+                new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        assertTrue(queue.isEmpty());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index de59603..fd605f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -503,6 +503,11 @@
         return ai;
     }
 
+    static ResolveInfo withPriority(ResolveInfo info, int priority) {
+        info.priority = priority;
+        return info;
+    }
+
     static ResolveInfo makeManifestReceiver(String packageName, String name) {
         return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
     }
@@ -1634,4 +1639,22 @@
         waitForIdle();
         verify(mAms, never()).enqueueOomAdjTargetLocked(any());
     }
+
+    /**
+     * Verify that expected events are triggered when a broadcast is finished.
+     */
+    @Test
+    public void testNotifyFinished() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        final BroadcastRecord record = makeBroadcastRecord(intent, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)));
+        enqueueBroadcast(record);
+
+        waitForIdle();
+        verify(mAms).notifyBroadcastFinishedLocked(eq(record));
+        verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED),
+                eq(1), eq(0), anyLong());
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 11573c5..05ed0e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,10 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -99,6 +100,16 @@
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
     }
 
     @Test
@@ -111,6 +122,17 @@
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 10),
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+        assertArrayEquals(new int[] {-1,-1,-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+        assertArrayEquals(new int[] {-1,-1,-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10),
+                        createResolveInfo(PACKAGE2, getAppId(2), 10),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
     }
 
     @Test
@@ -123,6 +145,19 @@
                 createResolveInfo(PACKAGE1, getAppId(1), 0),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+        assertArrayEquals(new int[] {0,1,2},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), -10),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+        assertArrayEquals(new int[] {0,0,2,3,3},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
     }
 
     @Test
@@ -543,4 +578,9 @@
     private static int getAppId(int i) {
         return Process.FIRST_APPLICATION_UID + i;
     }
+
+    private static boolean isPrioritized(List<Object> receivers) {
+        return BroadcastRecord.isPrioritized(
+                calculateBlockedUntilTerminalCount(receivers, false), false);
+    }
 }
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 2547347..6279b87 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -34,6 +34,8 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -51,6 +53,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
@@ -98,12 +101,23 @@
     @Mock
     private IPackageManager mIPackageManager;
 
-    static class InjectorForTest extends JobConcurrencyManager.Injector {
+    private static class InjectorForTest extends JobConcurrencyManager.Injector {
+        public final ArrayMap<JobServiceContext, JobStatus> contexts = new ArrayMap<>();
+
         @Override
         JobServiceContext createJobServiceContext(JobSchedulerService service,
                 JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
                 JobPackageTracker tracker, Looper looper) {
-            return mock(JobServiceContext.class);
+            final JobServiceContext context = mock(JobServiceContext.class);
+            doAnswer((Answer<Boolean>) invocationOnMock -> {
+                Object[] args = invocationOnMock.getArguments();
+                final JobStatus job = (JobStatus) args[0];
+                contexts.put(context, job);
+                doReturn(job).when(context).getRunningJobLocked();
+                return true;
+            }).when(context).executeRunnableJob(any(), anyInt());
+            contexts.put(context, null);
+            return context;
         }
     }
 
@@ -142,6 +156,13 @@
         doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
         doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
         mInjector = new InjectorForTest();
+        doAnswer((Answer<Long>) invocationOnMock -> {
+            Object[] args = invocationOnMock.getArguments();
+            final JobStatus job = (JobStatus) args[0];
+            return job.shouldTreatAsExpeditedJob()
+                    ? JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS
+                    : JobSchedulerService.Constants.DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
+        }).when(jobSchedulerService).getMinJobExecutionGuaranteeMs(any());
         mJobConcurrencyManager = new JobConcurrencyManager(jobSchedulerService, mInjector);
         mGracePeriodObserver = mock(GracePeriodObserver.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -167,32 +188,55 @@
         final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
         final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
         final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
-        mJobConcurrencyManager
+        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                 .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
+        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
     }
 
     @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
+        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
                 .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
+        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+    }
+
+    @Test
+    public void testPrepareForAssignmentDetermination_onlyPreferredUidOnly() {
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+            JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+            mJobConcurrencyManager.addRunningJobForTesting(job);
+        }
+
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+        }
+
+        final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+        final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+        final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+        final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+
+        assertEquals(0, idle.size());
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        assertEquals(0, stoppable.size());
+        assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
     }
 
     @Test
@@ -216,7 +260,8 @@
         mJobConcurrencyManager
                 .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
         mJobConcurrencyManager
-                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable);
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        Long.MAX_VALUE);
 
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
         for (int i = changed.size() - 1; i >= 0; --i) {
@@ -226,6 +271,169 @@
     }
 
     @Test
+    public void testDetermineAssignments_allPreferredUidOnly_shortTimeLeft() throws Exception {
+        mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+        setConcurrencyConfig(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+                new TypeConfig(WORK_TYPE_BG, 0, JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT));
+        for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT * 2; ++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);
+            spyOn(job);
+            doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+            if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+                mJobConcurrencyManager.addRunningJobForTesting(job);
+            } else {
+                mPendingJobQueue.add(job);
+            }
+        }
+
+        // Waiting time is too short, so we shouldn't create any extra contexts.
+        final long remainingTimeMs = JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS / 2;
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+            doReturn(remainingTimeMs)
+                    .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+        }
+
+        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<>();
+
+        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        minPreferredUidOnlyWaitingTimeMs);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        assertEquals(0, changed.size());
+    }
+
+    @Test
+    public void testDetermineAssignments_allPreferredUidOnly_mediumTimeLeft() throws Exception {
+        mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+        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 * 2; ++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);
+            spyOn(job);
+            doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+            if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+                mJobConcurrencyManager.addRunningJobForTesting(job);
+            } else {
+                mPendingJobQueue.add(job);
+                jobs.add(job);
+            }
+        }
+
+        // Waiting time is longer than the EJ waiting time, but shorter than regular job waiting
+        // time, so we should only create an extra context for an EJ.
+        final long remainingTimeMs = (JobConcurrencyManager.DEFAULT_MAX_WAIT_EJ_MS
+                + JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS) / 2;
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+            doReturn(remainingTimeMs)
+                    .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+        }
+
+        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<>();
+
+        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        minPreferredUidOnlyWaitingTimeMs);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        for (int i = changed.size() - 1; i >= 0; --i) {
+            jobs.remove(changed.valueAt(i).newJob);
+        }
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - 1, jobs.size());
+        assertEquals(1, changed.size());
+        JobStatus assignedJob = changed.valueAt(0).newJob;
+        assertTrue(assignedJob.shouldTreatAsExpeditedJob());
+    }
+
+    @Test
+    public void testDetermineAssignments_allPreferredUidOnly_longTimeLeft() throws Exception {
+        mConfigBuilder.setBoolean(JobConcurrencyManager.KEY_ENABLE_MAX_WAIT_TIME_BYPASS, true);
+        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 * 2; ++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);
+            spyOn(job);
+            doReturn(i % 2 == 0).when(job).shouldTreatAsExpeditedJob();
+            if (i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT) {
+                mJobConcurrencyManager.addRunningJobForTesting(job);
+            } else {
+                mPendingJobQueue.add(job);
+                jobs.add(job);
+            }
+        }
+
+        // Waiting time is longer than even the regular job waiting time, so we should
+        // create an extra context for an EJ, and potentially one for a regular job.
+        final long remainingTimeMs = 2 * JobConcurrencyManager.DEFAULT_MAX_WAIT_REGULAR_MS;
+        for (int i = 0; i < mInjector.contexts.size(); ++i) {
+            doReturn(true).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+            doReturn(remainingTimeMs)
+                    .when(mInjector.contexts.keyAt(i)).getRemainingGuaranteedTimeMs(anyLong());
+        }
+
+        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<>();
+
+        long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
+                .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+        assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+
+        mJobConcurrencyManager
+                .determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
+                        minPreferredUidOnlyWaitingTimeMs);
+
+        assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
+        // Depending on iteration order, we may create 1 or 2 contexts.
+        final long numAssignedJobs = changed.size();
+        assertTrue(numAssignedJobs > 0);
+        assertTrue(numAssignedJobs <= 2);
+        for (int i = 0; i < numAssignedJobs; ++i) {
+            jobs.remove(changed.valueAt(i).newJob);
+        }
+        assertEquals(numAssignedJobs,
+                JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT - jobs.size());
+        JobStatus firstAssignedJob = changed.valueAt(0).newJob;
+        if (!firstAssignedJob.shouldTreatAsExpeditedJob()) {
+            assertEquals(2, numAssignedJobs);
+            assertTrue(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
+        } else if (numAssignedJobs == 2) {
+            assertFalse(changed.valueAt(1).newJob.shouldTreatAsExpeditedJob());
+        }
+    }
+
+    @Test
     public void testIsPkgConcurrencyLimited_top() {
         final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
         topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index c15f6a9..bbcf77b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -326,7 +326,7 @@
 
         void register(Context context) {
             if (!mRegistered) {
-                context.registerReceiver(this, mFilter);
+                context.registerReceiver(this, mFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
                 mRegistered = true;
             }
         }
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index f28ad79..e54a48b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -3470,7 +3470,8 @@
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            return mMockContext.registerReceiver(receiver, filter);
+            return mMockContext.registerReceiver(receiver, filter,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index fe92a1d..935d1d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
 import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -403,7 +402,7 @@
         verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     @Test
@@ -424,7 +423,7 @@
         verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     @Test
@@ -531,7 +530,7 @@
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
                 mUserController.getRunningUsersLU());
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     /**
@@ -964,8 +963,8 @@
         verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
     }
 
-    private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
-        verify(mInjector).onUserStarting(userId, visible);
+    private void verifySystemUserVisibilityChangedNotified(boolean visible) {
+        verify(mInjector).notifySystemUserVisibilityChanged(visible);
     }
 
     // Should be public to allow mocking
@@ -1108,6 +1107,11 @@
         void onUserStarting(@UserIdInt int userId, boolean visible) {
             Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
         }
+
+        @Override
+        void notifySystemUserVisibilityChanged(boolean visible) {
+            Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 85d8aba..0d6d1a2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -64,6 +64,7 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -75,7 +76,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.statusbar.ISessionListener;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.server.biometrics.log.BiometricContextProvider;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
 import org.junit.Before;
@@ -129,6 +133,16 @@
     ITrustManager mTrustManager;
     @Mock
     DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private IStatusBarService mStatusBarService;
+    @Mock
+    private ISessionListener mSessionListener;
+    @Mock
+    private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+
+    BiometricContextProvider mBiometricContextProvider;
 
     @Before
     public void setUp() {
@@ -160,6 +174,11 @@
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
 
+        when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        mBiometricContextProvider = new BiometricContextProvider(mAmbientDisplayConfiguration,
+                mStatusBarService, null /* handler */, mAuthSessionCoordinator);
+        when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
+
         final String[] config = {
                 "0:2:15",  // ID0:Fingerprint:Strong
                 "1:8:15",  // ID1:Face:Strong
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
index c5a8557..ebf7fd8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthResultCoordinatorTest.java
@@ -61,11 +61,11 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
     }
 
     @Test
-    public void testLockout() {
+    public void testConvenientLockout() {
         mAuthResultCoordinator.lockedOutFor(
                 BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
 
@@ -80,7 +80,7 @@
     }
 
     @Test
-    public void testConvenientLockout() {
+    public void testConvenientUnlock() {
         mAuthResultCoordinator.authenticatedFor(
                 BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
 
@@ -91,11 +91,26 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
     }
 
     @Test
     public void testWeakLockout() {
+        mAuthResultCoordinator.lockedOutFor(
+                BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE);
+
+        Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult();
+
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
+                AUTHENTICATOR_DEFAULT);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
+                AUTHENTICATOR_DEFAULT);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+    }
+
+    @Test
+    public void testWeakUnlock() {
         mAuthResultCoordinator.authenticatedFor(
                 BiometricManager.Authenticators.BIOMETRIC_WEAK);
 
@@ -104,13 +119,29 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED);
+                AUTHENTICATOR_DEFAULT);
     }
 
     @Test
     public void testStrongLockout() {
+        mAuthResultCoordinator.lockedOutFor(
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+
+        Map<Integer, Integer> authMap = mAuthResultCoordinator.getResult();
+
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+        assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
+                AUTHENTICATOR_LOCKED);
+    }
+
+
+    @Test
+    public void testStrongUnlock() {
         mAuthResultCoordinator.authenticatedFor(
                 BiometricManager.Authenticators.BIOMETRIC_STRONG);
 
@@ -136,9 +167,9 @@
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_STRONG)).isEqualTo(
                 AUTHENTICATOR_DEFAULT);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_WEAK)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED | AUTHENTICATOR_LOCKED);
+                AUTHENTICATOR_LOCKED);
         assertThat(authMap.get(BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE)).isEqualTo(
-                AUTHENTICATOR_UNLOCKED | AUTHENTICATOR_LOCKED);
+                AUTHENTICATOR_LOCKED);
 
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
index 6e44875..c3b9cb1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
@@ -53,22 +53,28 @@
     }
 
     @Test
-    public void testUserUnlocked() {
+    public void testUserUnlockedWithWeak() {
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */,
                 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
-        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
-                0 /* requestId */);
+        mCoordinator.authEndedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
+                0 /* requestId */, true /* wasSuccessful */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
@@ -77,38 +83,79 @@
         mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */,
                 0 /* requestId */);
-        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
-                0 /* requestId */);
+        mCoordinator.authEndedFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
+                0 /* requestId */, false /* wasSuccessful */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
-        mCoordinator.authenticatedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
-                0 /* requestId */);
+        mCoordinator.authEndedFor(PRIMARY_USER, BIOMETRIC_WEAK, 1 /* sensorId */,
+                0 /* requestId */, false /* wasSuccessful */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+    }
+
+    @Test
+    public void testWeakAndConvenientCannotResetLockout() {
+        mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
+        mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_STRONG, 1 /* sensorId */,
+                0 /* requestId */);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+
+        mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_WEAK, 0 /* requestId */);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+
+        mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE, 0 /* requestId */);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testUserCanAuthDuringLockoutOfSameSession() {
         mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
         mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
                 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -123,25 +170,39 @@
 
         mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(
+                mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
 
         mCoordinator.authStartedFor(PRIMARY_USER, 1 /* sensorId */, 0 /* requestId */);
         mCoordinator.authStartedFor(PRIMARY_USER, 2 /* sensorId */, 0 /* requestId */);
         mCoordinator.lockedOutFor(PRIMARY_USER, BIOMETRIC_WEAK, 2 /* sensorId */,
                 0 /* requestId */);
 
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mCoordinator.getCanAuthFor(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mCoordinator.getCanAuthFor(SECONDARY_USER, BIOMETRIC_STRONG)).isFalse();
+        assertThat(
+                mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mCoordinator.getLockoutStateFor(SECONDARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 9f30c75..4c898b0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
+import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
@@ -78,17 +79,24 @@
     private static final int TEST_SENSOR_ID = 1;
     private static final int LOG_NUM_RECENT_OPERATIONS = 2;
     @Rule
-    public final TestableContext mContext =
-            new TestableContext(InstrumentationRegistry.getContext(), null);
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getContext(), null);
     private BiometricScheduler mScheduler;
     private IBinder mToken;
     @Mock
     private IBiometricService mBiometricService;
+    @Mock
+    private BiometricContext mBiometricContext;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mToken = new Binder();
+        when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
+                BIOMETRIC_SUCCESS);
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
         mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
                 BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
                 mBiometricService, LOG_NUM_RECENT_OPERATIONS);
@@ -98,10 +106,10 @@
     public void testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash() {
         final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
 
-        final HalClientMonitor<Object> client1 =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
-        final HalClientMonitor<Object> client2 =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+        final HalClientMonitor<Object> client1 = new TestHalClientMonitor(mContext, mToken,
+                nonNullDaemon);
+        final HalClientMonitor<Object> client2 = new TestHalClientMonitor(mContext, mToken,
+                nonNullDaemon);
         mScheduler.scheduleClientMonitor(client1);
         mScheduler.scheduleClientMonitor(client2);
 
@@ -112,10 +120,9 @@
     @Test
     public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
         // Even if second client has a non-null daemon, it needs to be canceled.
-        final TestHalClientMonitor client1 = new TestHalClientMonitor(
-                mContext, mToken, () -> null);
-        final TestHalClientMonitor client2 = new TestHalClientMonitor(
-                mContext, mToken, () -> mock(Object.class));
+        final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, () -> null);
+        final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
+                () -> mock(Object.class));
 
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
         final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
@@ -150,10 +157,10 @@
 
         final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
 
-        final TestAuthenticationClient client1 =
-                new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
-        final TestHalClientMonitor client2 =
-                new TestHalClientMonitor(mContext, mToken, () -> daemon2);
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, () -> null,
+                mToken, listener1, mBiometricContext);
+        final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
+                () -> daemon2);
 
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
         final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
@@ -188,15 +195,15 @@
     @Test
     public void testCancelNotInvoked_whenOperationWaitingForCookie() {
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
-        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, lazyDaemon1,
+                mToken, mock(ClientMonitorCallbackConverter.class), mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         // Schedule a BiometricPrompt authentication request
         mScheduler.scheduleClientMonitor(client1, callback1);
 
-        assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart(
-                mock(ClientMonitorCallback.class)));
+        assertNotEquals(0,
+                mScheduler.mCurrentOperation.isReadyToStart(mock(ClientMonitorCallback.class)));
         assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
         assertEquals(0, mScheduler.mPendingOperations.size());
 
@@ -304,7 +311,7 @@
         final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
-                mToken, callback);
+                mToken, callback, mBiometricContext);
 
         // Add a non-cancellable client, then add the auth client
         mScheduler.scheduleClientMonitor(client1);
@@ -367,7 +374,8 @@
         final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
         testCancelsWhenRequestId(requestId, cancelRequestId, started,
-                new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
+                new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback,
+                        mBiometricContext));
     }
 
     @Test
@@ -448,11 +456,11 @@
         final long requestId2 = 20;
         final Supplier<Object> lazyDaemon = () -> mock(Object.class);
         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
-        final TestAuthenticationClient client1 = new TestAuthenticationClient(
-                mContext, lazyDaemon, mToken, callback);
+        final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
         client1.setRequestId(requestId1);
-        final TestAuthenticationClient client2 = new TestAuthenticationClient(
-                mContext, lazyDaemon, mToken, callback);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback, mBiometricContext);
         client2.setRequestId(requestId2);
 
         mScheduler.scheduleClientMonitor(client1);
@@ -506,8 +514,8 @@
     @Test
     public void testClientDestroyed_afterFinish() {
         final Supplier<Object> nonNullDaemon = () -> mock(Object.class);
-        final TestHalClientMonitor client =
-                new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
+        final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
+                nonNullDaemon);
         mScheduler.scheduleClientMonitor(client);
         client.mCallback.onClientFinished(client, true /* success */);
         waitForIdle();
@@ -520,7 +528,8 @@
         final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -555,7 +564,8 @@
         final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -589,7 +599,8 @@
 
         //Run additional auth client
         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client2, callback2);
@@ -626,7 +637,8 @@
         final TestableLooper looper = TestableLooper.get(this);
         final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
-                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */);
+                lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
+                mBiometricContext);
         final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -679,19 +691,22 @@
 
         TestAuthenticationClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener) {
-            this(context, lazyDaemon, token, listener, 1 /* cookie */);
+                @NonNull ClientMonitorCallbackConverter listener,
+                BiometricContext biometricContext) {
+            this(context, lazyDaemon, token, listener, 1 /* cookie */, biometricContext);
         }
 
         TestAuthenticationClient(@NonNull Context context,
                 @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener, int cookie) {
+                @NonNull ClientMonitorCallbackConverter listener, int cookie,
+                @NonNull BiometricContext biometricContext) {
             super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
                     false /* restricted */, TAG, cookie, false /* requireConfirmation */,
-                    TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class),
+                    TEST_SENSOR_ID, mock(BiometricLogger.class), biometricContext,
                     true /* isStrongBiometric */, null /* taskStackListener */,
-                    mock(LockoutTracker.class), false /* isKeyguard */,
-                    true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+                    null /* lockoutTracker */, false /* isKeyguard */,
+                    true /* shouldVibrate */, false /* isKeyguardBypassEnabled */,
+                    0 /* sensorStrength */);
         }
 
         @Override
@@ -742,14 +757,12 @@
         boolean mStoppedHal = false;
         int mNumCancels = 0;
 
-        TestEnrollClient(@NonNull Context context,
-                @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token,
-                @NonNull ClientMonitorCallbackConverter listener) {
+        TestEnrollClient(@NonNull Context context, @NonNull Supplier<Object> lazyDaemon,
+                @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener) {
             super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
-                    "test" /* owner */, mock(BiometricUtils.class),
-                    5 /* timeoutSec */, TEST_SENSOR_ID,
-                    true /* shouldVibrate */,
-                    mock(BiometricLogger.class), mock(BiometricContext.class));
+                    "test" /* owner */, mock(BiometricUtils.class), 5 /* timeoutSec */,
+                    TEST_SENSOR_ID, true /* shouldVibrate */, mock(BiometricLogger.class),
+                    mock(BiometricContext.class));
         }
 
         @Override
@@ -787,9 +800,9 @@
 
         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
                 @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) {
-            super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
-                    TAG, cookie, TEST_SENSOR_ID,
-                    mock(BiometricLogger.class), mock(BiometricContext.class));
+            super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG,
+                    cookie, TEST_SENSOR_ID, mock(BiometricLogger.class),
+                    mock(BiometricContext.class));
             mProtoEnum = protoEnum;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
index 0b10a7b..968844e4c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/MultiBiometricLockoutStateTest.java
@@ -50,16 +50,22 @@
 
     private static void unlockAllBiometrics(MultiBiometricLockoutState lockoutState, int userId) {
         lockoutState.setAuthenticatorTo(userId, BIOMETRIC_STRONG, true /* canAuthenticate */);
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isTrue();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isTrue();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     private static void lockoutAllBiometrics(MultiBiometricLockoutState lockoutState, int userId) {
         lockoutState.setAuthenticatorTo(userId, BIOMETRIC_STRONG, false /* canAuthenticate */);
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_STRONG)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_WEAK)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userId, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userId, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     private void unlockAllBiometrics() {
@@ -79,9 +85,12 @@
 
     @Test
     public void testInitialStateLockedOut() {
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -89,20 +98,26 @@
         unlockAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_CONVENIENCE,
                 false /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testWeakLockout() {
         unlockAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_WEAK, false /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
@@ -110,10 +125,13 @@
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_STRONG,
                 false /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
@@ -121,18 +139,24 @@
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_CONVENIENCE,
                 true /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
     public void testWeakUnlock() {
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_WEAK, true /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -140,9 +164,12 @@
         lockoutAllBiometrics();
         mLockoutState.setAuthenticatorTo(PRIMARY_USER, BIOMETRIC_STRONG,
                 true /* canAuthenticate */);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 
     @Test
@@ -154,45 +181,66 @@
         lockoutAllBiometrics(lockoutState, userTwo);
 
         lockoutState.setAuthenticatorTo(userOne, BIOMETRIC_WEAK, true /* canAuthenticate */);
-        assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_STRONG)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_WEAK)).isTrue();
-        assertThat(lockoutState.canUserAuthenticate(userOne, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(lockoutState.getLockoutState(userOne, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userOne, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(lockoutState.getLockoutState(userOne, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
-        assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_STRONG)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_WEAK)).isFalse();
-        assertThat(lockoutState.canUserAuthenticate(userTwo, BIOMETRIC_CONVENIENCE)).isFalse();
+        assertThat(lockoutState.getLockoutState(userTwo, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userTwo, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
+        assertThat(lockoutState.getLockoutState(userTwo, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_PERMANENT);
     }
 
     @Test
     public void testTimedLockout() {
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
         mLockoutState.increaseLockoutTime(PRIMARY_USER, BIOMETRIC_STRONG,
                 System.currentTimeMillis() + 1);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
     }
 
     @Test
     public void testTimedLockoutAfterDuration() {
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
 
         when(mClock.millis()).thenReturn(0L);
         mLockoutState.increaseLockoutTime(PRIMARY_USER, BIOMETRIC_STRONG, 1);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isFalse();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isFalse();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
         assertThat(
-                mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isFalse();
+                mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_TIMED);
 
         when(mClock.millis()).thenReturn(2L);
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_STRONG)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_WEAK)).isTrue();
-        assertThat(mLockoutState.canUserAuthenticate(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isTrue();
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
+        assertThat(mLockoutState.getLockoutState(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
+                LockoutTracker.LOCKOUT_NONE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 2dc3583..d10d7d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -47,7 +47,6 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
 import org.junit.Before;
@@ -85,8 +84,6 @@
     @Mock
     private BiometricContext mBiometricContext;
     @Mock
-    private LockoutCache mLockoutCache;
-    @Mock
     private UsageStats mUsageStats;
     @Mock
     private ClientMonitorCallback mCallback;
@@ -161,7 +158,7 @@
                 false /* restricted */, "test-owner", 4 /* cookie */,
                 false /* requireConfirmation */, 9 /* sensorId */,
                 mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
-                mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */,
+                mUsageStats, null /* mLockoutCache */, false /* allowBackgroundAuthentication */,
                 false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */,
                 0 /* biometricStrength */) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 2afc4d7..1f29bec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors.face.aidl;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -25,7 +26,10 @@
 
 import android.content.Context;
 import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
 import android.os.Handler;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
@@ -36,6 +40,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -74,12 +79,18 @@
     private BiometricContext mBiometricContext;
     @Mock
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private IFace mDaemon;
+    @Mock
+    private BiometricStateCallback mBiometricStateCallback;
 
     private final TestLooper mLooper = new TestLooper();
     private final LockoutCache mLockoutCache = new LockoutCache();
 
     private UserAwareBiometricScheduler mScheduler;
     private Sensor.HalSessionCallback mHalCallback;
+    private FaceProvider mFaceProvider;
+    private SensorProps[] mSensorProps;
 
     @Before
     public void setUp() {
@@ -99,6 +110,16 @@
         mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
                 TAG, mScheduler, SENSOR_ID,
                 USER_ID, mLockoutCache, mLockoutResetDispatcher, mHalSessionCallback);
+
+        final SensorProps sensor1 = new SensorProps();
+        sensor1.commonProps = new CommonProps();
+        sensor1.commonProps.sensorId = 0;
+        final SensorProps sensor2 = new SensorProps();
+        sensor2.commonProps = new CommonProps();
+        sensor2.commonProps.sensorId = 1;
+        mSensorProps = new SensorProps[]{sensor1, sensor2};
+        mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
+                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
     }
 
     @Test
@@ -128,6 +149,18 @@
         verifyNotLocked();
     }
 
+    @Test
+    public void onBinderDied_noErrorOnNullClient() {
+        mScheduler.reset();
+        assertNull(mScheduler.getCurrentClient());
+        mFaceProvider.binderDied();
+
+        for (int i = 0; i < mFaceProvider.mSensors.size(); i++) {
+            final Sensor sensor = mFaceProvider.mSensors.valueAt(i);
+            assertNull(sensor.getSessionForUser(USER_ID));
+        }
+    }
+
     private void verifyNotLocked() {
         assertEquals(LockoutTracker.LOCKOUT_NONE, mLockoutCache.getLockoutModeForUser(USER_ID));
         verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(eq(SENSOR_ID));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 675f0e3..22d1bc5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -63,7 +63,6 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutCache;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -114,8 +113,6 @@
     @Mock
     private BiometricManager mBiometricManager;
     @Mock
-    private LockoutCache mLockoutCache;
-    @Mock
     private IUdfpsOverlayController mUdfpsOverlayController;
     @Mock
     private ISidefpsController mSideFpsController;
@@ -620,6 +617,20 @@
         verify(mCallback).onClientFinished(any(), eq(true));
     }
 
+    @Test
+    public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        client.onPowerPressed();
+        mLooper.dispatchAll();
+
+        verify(mCallback, never()).onClientFinished(any(), eq(true));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
     }
@@ -644,7 +655,7 @@
                 false /* requireConfirmation */,
                 9 /* sensorId */, mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
-                null /* taskStackListener */, mLockoutCache,
+                null /* taskStackListener */, null /* lockoutCache */,
                 mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index e991ec6..ac1667d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -442,7 +442,8 @@
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
         mMockSystemServices.registerReceiver(receiver, filter, null);
-        return spiedContext.registerReceiver(receiver, filter);
+        return spiedContext.registerReceiver(receiver, filter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 1fe267f..9e61cab 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -795,6 +795,71 @@
     }
 
     @Test
+    @Parameters({
+            "true",
+            "false"
+    })
+    public void testVotingWithSwitchingTypeRenderFrameRateOnly(boolean frameRateIsRefreshRate) {
+        when(mInjector.renderFrameRateIsPhysicalRefreshRate()).thenReturn(frameRateIsRefreshRate);
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_NONE);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        if (frameRateIsRefreshRate) {
+            assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        } else {
+            assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        }
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        if (frameRateIsRefreshRate) {
+            assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(
+                    60);
+        } else {
+            assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        }
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        if (frameRateIsRefreshRate) {
+            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        } else {
+            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        }
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        if (frameRateIsRefreshRate) {
+            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        }
+
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+    }
+
+    @Test
     public void testVotingWithSwitchingTypeWithinGroups() {
         DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
 
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index f9b8373..9672085 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -108,7 +108,7 @@
         }
 
         @Override
-        public boolean hasFsverity(String path) {
+        public boolean isFromTrustedProvider(String path, byte[] signature) {
             return mHasFsverityPaths.contains(path);
         }
 
@@ -291,6 +291,32 @@
     }
 
     @Test
+    public void construct_missingSignatureFile() throws Exception {
+        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
+                mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE)));
+        assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+
+        // Remove signature file next to the font file.
+        File fontDir = dirForPreparation.getPostScriptMap().get("foo");
+        File sigFile = new File(fontDir.getParentFile(), "font.fsv_sig");
+        assertThat(sigFile.exists()).isTrue();
+        sigFile.delete();
+
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
+                mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+        dir.loadFontFileMap();
+        // The font file should be removed and should not be loaded.
+        assertThat(dir.getPostScriptMap()).isEmpty();
+        assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+        assertThat(dir.getFontFamilyMap()).isEmpty();
+    }
+
+    @Test
     public void construct_olderThanPreinstalledFont() throws Exception {
         Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
             FontConfig.Font fooFont = new FontConfig.Font(
@@ -782,8 +808,8 @@
         UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
 
             @Override
-            public boolean hasFsverity(String path) {
-                return mFakeFsverityUtil.hasFsverity(path);
+            public boolean isFromTrustedProvider(String path, byte[] signature) {
+                return mFakeFsverityUtil.isFromTrustedProvider(path, signature);
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index d91a748..9c8e72c 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -115,7 +115,8 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(ACTION_JOB_STARTED);
         intentFilter.addAction(ACTION_JOB_STOPPED);
-        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter);
+        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
         setAppOpsModeAllowed(true);
         setPowerExemption(false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java
new file mode 100644
index 0000000..2c5d97d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/AppUpdateTrackerTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locales;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Binder;
+import android.os.LocaleList;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link AppUpdateTracker}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppUpdateTrackerTest {
+    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
+    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+    private static final int DEFAULT_USER_ID = 0;
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
+    private static final LocaleList DEFAULT_LOCALES = LocaleList.forLanguageTags(
+            DEFAULT_LOCALE_TAGS);
+    private AppUpdateTracker mAppUpdateTracker;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private LocaleManagerService mMockLocaleManagerService;
+    @Mock
+    private ShadowLocaleManagerBackupHelper mMockBackupHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mMockLocaleManagerService = mock(LocaleManagerService.class);
+        mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
+        mAppUpdateTracker = spy(
+                new AppUpdateTracker(mMockContext, mMockLocaleManagerService, mMockBackupHelper));
+    }
+
+    @Test
+    public void testPackageUpgraded_localeEmpty_doNothing() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+    }
+
+    @Test
+    public void testPackageUpgraded_pkgNotInSp_doNothing() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+    }
+
+    @Test
+    public void testPackageUpgraded_appLocalesSupported_doNothing() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(DEFAULT_LOCALES, DEFAULT_PACKAGE_NAME);
+
+        setUpAppLocalesOptIn(true);
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+
+        setUpAppLocalesOptIn(false);
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+
+        setUpAppLocalesOptIn(false);
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verifyNoLocalesCleared();
+    }
+
+    @Test
+    public void testPackageUpgraded_appLocalesNotSupported_clearAppLocale() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(null, DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+
+        setUpPackageLocaleConfig(LocaleList.getEmptyLocaleList(), DEFAULT_PACKAGE_NAME);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(2)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+
+        setUpAppLocalesOptIn(false);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(3)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+    }
+
+    @Test
+    public void testPackageUpgraded_appLocalesNotInLocaleConfig_clearAppLocale() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        setUpPackageLocaleConfig(LocaleList.forLanguageTags("hi,fr"), DEFAULT_PACKAGE_NAME);
+        setUpAppLocalesOptIn(true);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+
+        setUpAppLocalesOptIn(false);
+
+        mAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+        verify(mMockLocaleManagerService, times(2)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, LocaleList.forLanguageTags(""), false);
+    }
+
+    private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
+        doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(eq(packageName),
+                anyInt());
+    }
+
+    private void setUpPackageNamesForSp(Set<String> packageNames) {
+        SharedPreferences mockSharedPreference = mock(SharedPreferences.class);
+        doReturn(mockSharedPreference).when(mMockBackupHelper).getPersistedInfo();
+        doReturn(packageNames).when(mockSharedPreference).getStringSet(anyString(), any());
+    }
+
+    private void setUpPackageLocaleConfig(LocaleList locales, String packageName) {
+        doReturn(locales).when(mAppUpdateTracker).getPackageLocales(eq(packageName), anyInt());
+    }
+
+    private void setUpAppLocalesOptIn(boolean optIn) {
+        doReturn(optIn).when(mAppUpdateTracker).isSettingsAppLocalesOptIn();
+    }
+
+    /**
+     * Verifies that no app locales needs to be cleared for any package.
+     *
+     * <p>If {@link LocaleManagerService#setApplicationLocales} is not invoked when receiving the
+     * callback of package upgraded, we can conclude that no app locales needs to be cleared.
+     */
+    private void verifyNoLocalesCleared() throws Exception {
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
+                any(), anyBoolean());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 8f0fb0b..4d42afa 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -18,9 +18,11 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -35,6 +37,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -44,6 +47,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SimpleClock;
+import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.Xml;
 
@@ -53,6 +57,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.locales.LocaleManagerBackupHelper.LocalesInfo;
 
 import org.junit.After;
 import org.junit.Before;
@@ -69,9 +74,12 @@
 import java.time.Clock;
 import java.time.Duration;
 import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Unit tests for the {@link LocaleManagerInternal}.
@@ -89,8 +97,8 @@
     private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
     private static final LocaleList DEFAULT_LOCALES =
             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
-    private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of(
-            DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS);
+    private static final Map<String, LocalesInfo> DEFAULT_PACKAGE_LOCALES_INFO_MAP = Map.of(
+            DEFAULT_PACKAGE_NAME, new LocalesInfo(DEFAULT_LOCALE_TAGS, false));
     private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA =
             new SparseArray<>();
 
@@ -103,6 +111,10 @@
     private PackageManager mMockPackageManager;
     @Mock
     private LocaleManagerService mMockLocaleManagerService;
+    @Mock
+    private SharedPreferences mMockDelegateAppLocalePackages;
+    @Mock
+    private SharedPreferences.Editor mMockSpEditor;
 
     BroadcastReceiver mUserMonitor;
     PackageMonitor mPackageMonitor;
@@ -127,9 +139,13 @@
         mMockContext = mock(Context.class);
         mMockPackageManager = mock(PackageManager.class);
         mMockLocaleManagerService = mock(LocaleManagerService.class);
+        mMockDelegateAppLocalePackages = mock(SharedPreferences.class);
+        mMockSpEditor = mock(SharedPreferences.Editor.class);
         SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
+        AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
 
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit();
 
         HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -137,12 +153,12 @@
 
         mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
                 mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
-                broadcastHandlerThread));
+                broadcastHandlerThread, mMockDelegateAppLocalePackages));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
         mUserMonitor = mBackupHelper.getUserMonitor();
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
-            systemAppUpdateTracker);
+            systemAppUpdateTracker, appUpdateTracker);
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
     }
 
@@ -171,9 +187,23 @@
     public void testBackupPayload_appLocalesSet_returnsNonNullBlob() throws Exception {
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
         setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+        setUpPackageNamesForSp(Collections.<String>emptySet());
 
         byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
-        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_INFO_MAP, payload);
+    }
+
+    @Test
+    public void testBackupPayload_appLocalesSet_fromDelegateSelector() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+        Map<String, LocalesInfo> expectPackageLocalePack = Map.of(DEFAULT_PACKAGE_NAME,
+                new LocalesInfo(DEFAULT_LOCALE_TAGS, true));
+
+        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
+
+        verifyPayloadForAppLocales(expectPackageLocalePack, payload);
     }
 
     @Test
@@ -195,14 +225,15 @@
         anotherAppInfo.packageName = "com.android.anotherapp";
         doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpPackageNamesForSp(Collections.<String>emptySet());
         // Exception when getting locales for anotherApp.
         doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
                 eq(anotherAppInfo.packageName), anyInt());
 
         byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
-        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_INFO_MAP, payload);
     }
 
     @Test
@@ -226,8 +257,7 @@
     @Test
     public void testRestore_allAppsInstalled_noStageDataCreated() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
 
@@ -235,38 +265,104 @@
 
         // Locales were restored
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
-                DEFAULT_USER_ID, DEFAULT_LOCALES);
-
+                DEFAULT_USER_ID, DEFAULT_LOCALES, false);
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
+    public void testRestore_allAppsInstalled_nothingToSp() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES, false);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false,
+                false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+    }
+
+    @Test
+    public void testRestore_allAppsInstalled_storeInfoToSp() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        Map<String, LocalesInfo> pkgLocalesMap = Map.of(DEFAULT_PACKAGE_NAME,
+                new LocalesInfo(DEFAULT_LOCALE_TAGS, true));
+        writeTestPayload(out, pkgLocalesMap);
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
+                false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+                new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+    }
+
+    @Test
+    public void testRestore_allAppsInstalled_InfoHasExistedInSp() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        Map<String, LocalesInfo> pkgLocalesMap = Map.of(DEFAULT_PACKAGE_NAME,
+                new LocalesInfo(DEFAULT_LOCALE_TAGS, true));
+        writeTestPayload(out, pkgLocalesMap);
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(DEFAULT_PACKAGE_NAME)));
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+        checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
+                false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+    }
+
+    @Test
     public void testRestore_noAppsInstalled_everythingStaged() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
 
     @Test
     public void testRestore_someAppsInstalled_partiallyStaged() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, true);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
         writeTestPayload(out, pkgLocalesMap);
-
         setUpPackageInstalled(pkgNameA);
         setUpPackageNotInstalled(pkgNameB);
         setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
@@ -274,9 +370,10 @@
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsA));
+                LocaleList.forLanguageTags(langTagsA), true);
 
         pkgLocalesMap.remove(pkgNameA);
+
         verifyStageDataForUser(pkgLocalesMap,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
@@ -284,8 +381,7 @@
     @Test
     public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageData() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
 
@@ -300,19 +396,20 @@
     public void testRestore_appLocalesSetForSomeApps_restoresOnlyForAppsHavingNoLocalesSet()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String pkgNameC = "com.android.myAppC";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
         String langTagsC = "zh,es";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
-        pkgLocalesMap.put(pkgNameC, langTagsC);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, true);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        LocalesInfo localesInfoC = new LocalesInfo(langTagsC, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
+        pkgLocalesMap.put(pkgNameC, localesInfoC);
         writeTestPayload(out, pkgLocalesMap);
-
         // Both app A & B are installed on the device but A has locales already set.
         setUpPackageInstalled(pkgNameA);
         setUpPackageInstalled(pkgNameB);
@@ -320,20 +417,22 @@
         setUpLocalesForPackage(pkgNameA, LocaleList.forLanguageTags("mr,fr"));
         setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
         setUpLocalesForPackage(pkgNameC, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB, pkgNameC)));
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         // Restore locales only for myAppB.
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
-                any());
+                any(), anyBoolean());
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsB));
+                LocaleList.forLanguageTags(langTagsB), true);
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
-                any());
+                any(), anyBoolean());
 
         // App C is staged.
         pkgLocalesMap.remove(pkgNameA);
         pkgLocalesMap.remove(pkgNameB);
+
         verifyStageDataForUser(pkgLocalesMap,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
     }
@@ -341,40 +440,41 @@
     @Test
     public void testRestore_restoreInvokedAgain_creationTimeChanged() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100;
         setCurrentTimeMillis(newCreationTime);
+
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 newCreationTime, DEFAULT_USER_ID);
     }
 
     @Test
     public void testRestore_appInstalledAfterSUW_restoresFromStage() throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
         writeTestPayload(out, pkgLocalesMap);
-
         setUpPackageNotInstalled(pkgNameA);
         setUpPackageNotInstalled(pkgNameB);
         setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
         setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+        setUpPackageNamesForSp(new ArraySet<>());
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
@@ -385,9 +485,14 @@
         mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsA));
+                LocaleList.forLanguageTags(langTagsA), false);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
 
         pkgLocalesMap.remove(pkgNameA);
+
         verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         setUpPackageInstalled(pkgNameB);
@@ -395,7 +500,12 @@
         mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsB));
+                LocaleList.forLanguageTags(langTagsB), true);
+
+        mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
+
+        verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+                new ArraySet<>(Arrays.asList(pkgNameB)));
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
@@ -403,15 +513,14 @@
     public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         // Package is not present on the device when the SUW restore is going on.
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // App is installed later (post SUW).
@@ -429,14 +538,13 @@
     public void testStageDataDeletion_backupPassRunAfterRetentionPeriod_stageDataDeleted()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
 
         mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
 
         verifyNothingRestored();
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // Retention period has not elapsed.
@@ -444,8 +552,8 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
         doReturn(List.of()).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
         checkStageDataExists(DEFAULT_USER_ID);
 
         // Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
@@ -453,8 +561,8 @@
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
         doReturn(List.of()).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
         checkStageDataExists(DEFAULT_USER_ID);
 
         // Retention period has now expired, stage data should be deleted.
@@ -462,8 +570,8 @@
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         doReturn(List.of()).when(mMockPackageManager)
                 .getInstalledApplicationsAsUser(any(), anyInt());
-        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
@@ -471,16 +579,16 @@
     public void testStageDataDeletion_lazyRestoreAfterRetentionPeriod_stageDataDeleted()
             throws Exception {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        HashMap<String, String> pkgLocalesMap = new HashMap<>();
-
+        HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
         String pkgNameA = "com.android.myAppA";
         String pkgNameB = "com.android.myAppB";
         String langTagsA = "ru";
         String langTagsB = "hi,fr";
-        pkgLocalesMap.put(pkgNameA, langTagsA);
-        pkgLocalesMap.put(pkgNameB, langTagsB);
+        LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+        LocalesInfo localesInfoB = new LocalesInfo(langTagsB, false);
+        pkgLocalesMap.put(pkgNameA, localesInfoA);
+        pkgLocalesMap.put(pkgNameB, localesInfoB);
         writeTestPayload(out, pkgLocalesMap);
-
         setUpPackageNotInstalled(pkgNameA);
         setUpPackageNotInstalled(pkgNameB);
         setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
@@ -494,40 +602,40 @@
         // Retention period has not elapsed.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
-
         setUpPackageInstalled(pkgNameA);
+
         mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
 
-        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
-                LocaleList.forLanguageTags(langTagsA));
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
+                pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false);
 
         pkgLocalesMap.remove(pkgNameA);
+
         verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
 
         // Retention period has now expired, stage data should be deleted.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
         setUpPackageInstalled(pkgNameB);
+
         mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
 
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
-                any());
-
+                any(), anyBoolean());
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
     }
 
     @Test
     public void testUserRemoval_userRemoved_stageDataDeleted() throws Exception {
         final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
-        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_MAP);
-
+        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
         final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream();
         String anotherPackage = "com.android.anotherapp";
         String anotherLangTags = "mr,zh";
-        HashMap<String, String> pkgLocalesMapWorkProfile = new HashMap<>();
-        pkgLocalesMapWorkProfile.put(anotherPackage, anotherLangTags);
+        LocalesInfo localesInfo = new LocalesInfo(anotherLangTags, false);
+        HashMap<String, LocalesInfo> pkgLocalesMapWorkProfile = new HashMap<>();
+        pkgLocalesMapWorkProfile.put(anotherPackage, localesInfo);
         writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
-
         // DEFAULT_PACKAGE_NAME is NOT installed on the device.
         setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
         setUpPackageNotInstalled(anotherPackage);
@@ -537,8 +645,7 @@
                 WORK_PROFILE_USER_ID);
 
         verifyNothingRestored();
-
-        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_MAP,
+        verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
                 DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
         verifyStageDataForUser(pkgLocalesMapWorkProfile,
                 DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
@@ -554,6 +661,45 @@
                 DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
     }
 
+    @Test
+    public void testPackageRemoved_noInfoInSp() throws Exception {
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        setUpPackageNamesForSp(new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB)));
+
+        mPackageMonitor.onPackageRemoved(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+    }
+
+    @Test
+    public void testPackageRemoved_removeInfoFromSp() throws Exception {
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
+        setUpPackageNamesForSp(pkgNames);
+
+        mPackageMonitor.onPackageRemoved(pkgNameA, DEFAULT_UID);
+        pkgNames.remove(pkgNameA);
+
+        verify(mMockSpEditor, times(1)).putStringSet(
+                Integer.toString(DEFAULT_USER_ID), pkgNames);
+    }
+
+    @Test
+    public void testPackageDataCleared_removeInfoFromSp() throws Exception {
+        String pkgNameA = "com.android.myAppA";
+        String pkgNameB = "com.android.myAppB";
+        Set<String> pkgNames = new ArraySet<>(Arrays.asList(pkgNameA, pkgNameB));
+        setUpPackageNamesForSp(pkgNames);
+
+        mPackageMonitor.onPackageDataCleared(pkgNameB, DEFAULT_UID);
+        pkgNames.remove(pkgNameB);
+
+        verify(mMockSpEditor, times(1)).putStringSet(
+                Integer.toString(DEFAULT_USER_ID), pkgNames);
+    }
+
     private void setUpPackageInstalled(String packageName) throws Exception {
         doReturn(new PackageInfo()).when(mMockPackageManager).getPackageInfoAsUser(
                 eq(packageName), anyInt(), anyInt());
@@ -576,6 +722,11 @@
                 .getInstalledApplicationsAsUser(any(), anyInt());
     }
 
+    private void setUpPackageNamesForSp(Set<String> packageNames) {
+        doReturn(packageNames).when(mMockDelegateAppLocalePackages).getStringSet(anyString(),
+                any());
+    }
+
     /**
      * Verifies that nothing was restored for any package.
      *
@@ -584,31 +735,34 @@
      */
     private void verifyNothingRestored() throws Exception {
         verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
-                any());
+                any(), anyBoolean());
     }
 
-    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
+    private static void verifyPayloadForAppLocales(Map<String, LocalesInfo> expectedPkgLocalesMap,
             byte[] payload)
             throws IOException, XmlPullParserException {
         final ByteArrayInputStream stream = new ByteArrayInputStream(payload);
         final TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(stream, StandardCharsets.UTF_8.name());
 
-        Map<String, String> backupDataMap = new HashMap<>();
+        Map<String, LocalesInfo> backupDataMap = new HashMap<>();
         XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG);
         int depth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, depth)) {
             if (parser.getName().equals("package")) {
                 String packageName = parser.getAttributeValue(null, "name");
                 String languageTags = parser.getAttributeValue(null, "locales");
-                backupDataMap.put(packageName, languageTags);
+                boolean delegateSelector = parser.getAttributeBoolean(null, "delegate_selector");
+                LocalesInfo localesInfo = new LocalesInfo(languageTags, delegateSelector);
+                backupDataMap.put(packageName, localesInfo);
             }
         }
 
-        assertEquals(expectedPkgLocalesMap, backupDataMap);
+        verifyStageData(expectedPkgLocalesMap, backupDataMap);
     }
 
-    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap)
+    private static void writeTestPayload(OutputStream stream,
+            Map<String, LocalesInfo> pkgLocalesMap)
             throws IOException {
         if (pkgLocalesMap.isEmpty()) {
             return;
@@ -622,7 +776,9 @@
         for (String pkg : pkgLocalesMap.keySet()) {
             out.startTag(/* namespace= */ null, "package");
             out.attribute(/* namespace= */ null, "name", pkg);
-            out.attribute(/* namespace= */ null, "locales", pkgLocalesMap.get(pkg));
+            out.attribute(/* namespace= */ null, "locales", pkgLocalesMap.get(pkg).mLocales);
+            out.attributeBoolean(/* namespace= */ null, "delegate_selector",
+                    pkgLocalesMap.get(pkg).mSetFromDelegate);
             out.endTag(/*namespace= */ null, "package");
         }
 
@@ -630,12 +786,23 @@
         out.endDocument();
     }
 
-    private void verifyStageDataForUser(Map<String, String> expectedPkgLocalesMap,
+    private void verifyStageDataForUser(Map<String, LocalesInfo> expectedPkgLocalesMap,
             long expectedCreationTimeMillis, int userId) {
         LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId);
         assertNotNull(stagedDataForUser);
         assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis);
-        assertEquals(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
+        verifyStageData(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
+    }
+
+    private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap,
+            Map<String, LocalesInfo> stageData) {
+        assertEquals(expectedPkgLocalesMap.size(), stageData.size());
+        for (String pkg : expectedPkgLocalesMap.keySet()) {
+            assertTrue(stageData.containsKey(pkg));
+            assertEquals(expectedPkgLocalesMap.get(pkg).mLocales, stageData.get(pkg).mLocales);
+            assertEquals(expectedPkgLocalesMap.get(pkg).mSetFromDelegate,
+                    stageData.get(pkg).mSetFromDelegate);
+        }
     }
 
     private static void checkStageDataExists(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index dc9f907..79ed7d1 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -133,7 +133,7 @@
 
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                    LocaleList.getEmptyLocaleList());
+                    LocaleList.getEmptyLocaleList(), false);
             fail("Expected SecurityException");
         } finally {
             verify(mMockContext).enforceCallingOrSelfPermission(
@@ -148,7 +148,7 @@
     public void testSetApplicationLocales_nullPackageName_fails() throws Exception {
         try {
             mLocaleManagerService.setApplicationLocales(/* appPackageName = */ null,
-                    DEFAULT_USER_ID, LocaleList.getEmptyLocaleList());
+                    DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false);
             fail("Expected NullPointerException");
         } finally {
             verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -162,7 +162,7 @@
 
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                    /* locales = */ null);
+                    /* locales = */ null, false);
             fail("Expected NullPointerException");
         } finally {
             verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -180,7 +180,7 @@
         setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
 
         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                DEFAULT_LOCALES);
+                DEFAULT_LOCALES, true);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
         verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -193,7 +193,7 @@
                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
 
         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                DEFAULT_LOCALES);
+                DEFAULT_LOCALES, false);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
         verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -205,7 +205,7 @@
                 .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
-                    LocaleList.getEmptyLocaleList());
+                    LocaleList.getEmptyLocaleList(), false);
             fail("Expected IllegalArgumentException");
         } finally {
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index e403c87..9f7cbe3 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -17,6 +17,7 @@
 package com.android.server.locales;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.os.HandlerThread;
 import android.util.SparseArray;
@@ -33,8 +34,8 @@
             LocaleManagerService localeManagerService,
             PackageManager packageManager, Clock clock,
             SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
-            HandlerThread broadcastHandlerThread) {
+            HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
         super(context, localeManagerService, packageManager, clock, stagedData,
-                broadcastHandlerThread);
+                broadcastHandlerThread, delegateAppLocalePackages);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index ee97466..dc0740a 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -135,8 +135,9 @@
         mSystemAppUpdateTracker = new SystemAppUpdateTracker(mMockContext,
             mLocaleManagerService, mStoragefile);
 
-        mPackageMonitor = new LocaleManagerServicePackageMonitor(
-                mockLocaleManagerBackupHelper, mSystemAppUpdateTracker);
+        AppUpdateTracker appUpdateTracker = mock(AppUpdateTracker.class);
+        mPackageMonitor = new LocaleManagerServicePackageMonitor(mockLocaleManagerBackupHelper,
+                mSystemAppUpdateTracker, appUpdateTracker);
     }
 
     @After
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
index ea3c5fa..d9ebb4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
@@ -62,7 +62,7 @@
                 context.unregisterReceiver(this);
                 latch.countDown();
             }
-        }, new IntentFilter(TEST_INTENT_ACTION));
+        }, new IntentFilter(TEST_INTENT_ACTION), Context.RECEIVER_EXPORTED_UNAUDITED);
 
         mStorage.setSnapshotListener(recoveryAgentUid, intent);
 
@@ -83,7 +83,8 @@
                 latch.countDown();
             }
         };
-        context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION));
+        context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         mStorage.setSnapshotListener(recoveryAgentUid, intent);
         mStorage.setSnapshotListener(recoveryAgentUid, intent);
diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
index eaa0e9b..f0d389b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
@@ -34,6 +34,7 @@
 
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.PackageStateUnserialized;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -46,12 +47,16 @@
 
     private boolean mCompatibilityModeEnabled;;
     private PackageImpl mMockAndroidPackage;
+    private PackageSetting mMockPackageState;
     private PackageUserStateImpl mMockUserState;
 
     @Before
     public void setUp() {
         mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
         mMockAndroidPackage = mock(PackageImpl.class);
+        mMockPackageState = mock(PackageSetting.class);
+        when(mMockPackageState.getTransientState())
+                .thenReturn(new PackageStateUnserialized(mMockPackageState));
         mMockUserState = new PackageUserStateImpl();
         mMockUserState.setInstalled(true);
     }
@@ -221,7 +226,7 @@
         info.flags |= flags;
         when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
         return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
-                0 /*flags*/, mMockUserState, 0 /*userId*/, null);
+                0 /*flags*/, mMockUserState, 0 /*userId*/, mMockPackageState);
     }
 
     private void setGlobalCompatibilityMode(boolean enabled) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 9ce99d6..59f27ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -676,27 +676,32 @@
         final File testFile = extractFile(TEST_APP4_APK);
         try {
             final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            var pkgSetting = mockPkgSetting(pkg);
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserStateInternal.DEFAULT, 0, null);
+                    PackageUserStateInternal.DEFAULT, 0, pkgSetting);
             for (ParsedActivity activity : pkg.getActivities()) {
                 assertNotNull(activity.getMetaData());
                 assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedProvider provider : pkg.getProviders()) {
                 assertNotNull(provider.getMetaData());
                 assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedActivity receiver : pkg.getReceivers()) {
                 assertNotNull(receiver.getMetaData());
                 assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedService service : pkg.getServices()) {
                 assertNotNull(service.getMetaData());
                 assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
         } finally {
             testFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8efcc41..8d2cffa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -59,9 +59,11 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
+                .setShowInSettings(45)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
+        actualProps.setShowInSettings(32);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -99,10 +101,12 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
+                .setShowInSettings(3452)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
         orig.setStartWithParent(false);
+        orig.setShowInSettings(1437);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
         for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -140,6 +144,9 @@
         assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
+        assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
+                hasManagePermission);
+
         // Items requiring hasQueryPermission - put them here using hasQueryPermission.
 
         // Items with no permission requirements.
@@ -182,5 +189,6 @@
         assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+        assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c1e778d..df2c5dd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -599,6 +599,7 @@
         // Check that this new user has the expected properties (relative to the defaults)
         // provided that the test caller has the necessary permissions.
         assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+        assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
         assertThrows(SecurityException.class, userProps::getStartWithParent);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 9cd97ff3..699601b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -20,21 +20,16 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.apex.ApexInfo;
 import android.content.Context;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionInfo;
-import android.content.pm.SigningDetails;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 import android.util.SparseIntArray;
@@ -53,7 +48,6 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import com.google.common.truth.Expect;
 
@@ -63,7 +57,6 @@
 
 import java.io.File;
 import java.io.InputStream;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -567,54 +560,6 @@
     }
 
     @Test
-    public void testApexPackageInfoGeneration() throws Exception {
-        String apexModuleName = "com.android.tzdata.apex";
-        File apexFile = copyRawResourceToFile(apexModuleName,
-                R.raw.com_android_tzdata);
-        ApexInfo apexInfo = new ApexInfo();
-        apexInfo.isActive = true;
-        apexInfo.isFactory = false;
-        apexInfo.moduleName = apexModuleName;
-        apexInfo.modulePath = apexFile.getPath();
-        apexInfo.versionCode = 191000070;
-        int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
-
-        ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
-                flags, Collections.emptyList(), false /*collectCertificates*/);
-        if (result.isError()) {
-            throw new IllegalStateException(result.getErrorMessage(), result.getException());
-        }
-
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        ParsedPackage pkg = result.getResult();
-        ParseResult<SigningDetails> ret = ParsingPackageUtils.getSigningDetails(
-                input, pkg, false /*skipVerify*/);
-        if (ret.isError()) {
-            throw new IllegalStateException(ret.getErrorMessage(), ret.getException());
-        }
-        pkg.setSigningDetails(ret.getResult());
-        PackageInfo pi = PackageInfoUtils.generate(pkg.setApex(true).hideAsFinal(), apexInfo,
-                flags, null, UserHandle.USER_SYSTEM);
-
-        assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
-        assertTrue(pi.applicationInfo.enabled);
-        assertEquals(28, pi.applicationInfo.targetSdkVersion);
-        assertEquals(191000070, pi.applicationInfo.longVersionCode);
-        assertNotNull(pi.applicationInfo.metaData);
-        assertEquals(apexFile.getPath(), pi.applicationInfo.sourceDir);
-        assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]",
-                pi.applicationInfo.metaData.toString());
-
-        assertEquals("com.google.android.tzdata", pi.packageName);
-        assertEquals(191000070, pi.getLongVersionCode());
-        assertNotNull(pi.signingInfo);
-        assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
-        assertTrue(pi.isApex);
-        assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
-        assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
-    }
-
-    @Test
     public void testUsesSdk() throws Exception {
         ParsedPackage pkg;
         SparseIntArray minExtVers;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index a98a43b..93464cd 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -25,7 +25,7 @@
 import android.app.time.TimeCapabilitiesAndConfig;
 import android.app.time.TimeConfiguration;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -33,17 +33,17 @@
 /** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
 public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
 
-    private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+    private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
     private ConfigurationInternal mConfigurationInternal;
 
     @Override
-    public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void addConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.add(listener);
     }
 
     @Override
-    public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.remove(listener);
     }
 
@@ -86,7 +86,7 @@
     }
 
     void simulateConfigurationChangeForTests() {
-        for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
             listener.onChange();
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 62dae48..caef494 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -40,7 +40,7 @@
 
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -1821,7 +1821,7 @@
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
         private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
-        private ConfigurationChangeListener mConfigurationInternalChangeListener;
+        private StateChangeListener mConfigurationInternalChangeListener;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
@@ -1837,7 +1837,7 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+        public void setConfigurationInternalChangeListener(StateChangeListener listener) {
             mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 7140097..153d746 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -57,7 +57,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_autoDetectionSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
@@ -82,10 +83,7 @@
             assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,7 +99,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -117,10 +115,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +132,7 @@
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -150,7 +146,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_autoDetectNotSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
@@ -175,10 +172,7 @@
             assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -189,7 +183,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -205,10 +199,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -219,7 +211,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -233,7 +225,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
@@ -258,10 +251,7 @@
             assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,7 +266,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -292,10 +282,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,7 +296,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -316,7 +304,8 @@
 
     @Test
     public void test_telephonyFallbackSupported() {
-        ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal config = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
@@ -331,7 +320,8 @@
     /** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
     @Test
     public void test_geoDetectionRunInBackgroundEnabled() {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fdee86e..fc6afe4 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -16,14 +16,11 @@
 
 package com.android.server.timezonedetector;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 
 import java.time.Duration;
@@ -31,76 +28,104 @@
 import java.util.List;
 import java.util.Optional;
 
-/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
+/**
+ * A partially implemented, fake implementation of ServiceConfigAccessor for tests.
+ *
+ * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * simulate that some settings are global and shared between users. It also delivers config updates
+ * synchronously.
+ */
 public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
 
-    private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+    private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
-    private ConfigurationInternal mConfigurationInternal;
+    private ConfigurationInternal mCurrentUserConfigurationInternal;
+    private ConfigurationInternal mOtherUserConfigurationInternal;
 
     @Override
-    public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void addConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.add(listener);
     }
 
     @Override
-    public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.remove(listener);
     }
 
     @Override
     public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mConfigurationInternal;
+        return getConfigurationInternal(mCurrentUserConfigurationInternal.getUserId());
     }
 
     @Override
     public boolean updateConfiguration(
-            @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges,
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration requestedChanges,
             boolean bypassUserPolicyChecks) {
-        assertNotNull(mConfigurationInternal);
+        assertNotNull(mCurrentUserConfigurationInternal);
         assertNotNull(requestedChanges);
 
+        ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
         // Simulate the real strategy's behavior: the new configuration will be updated to be the
-        // old configuration merged with the new if the user has the capability to up the settings.
-        // Then, if the configuration changed, the change listener is invoked.
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+        // old configuration merged with the new if the user has the capability to update the
+        // settings. Then, if the configuration changed, the change listener is invoked.
+        TimeZoneCapabilities capabilities = toUpdate.asCapabilities(bypassUserPolicyChecks);
+        TimeZoneConfiguration configuration = toUpdate.asConfiguration();
         TimeZoneConfiguration newConfiguration =
                 capabilities.tryApplyConfigChanges(configuration, requestedChanges);
         if (newConfiguration == null) {
             return false;
         }
 
-        if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
-            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
-
+        if (!newConfiguration.equals(configuration)) {
+            ConfigurationInternal updatedConfiguration = toUpdate.merge(newConfiguration);
+            if (updatedConfiguration.getUserId() == mCurrentUserConfigurationInternal.getUserId()) {
+                mCurrentUserConfigurationInternal = updatedConfiguration;
+            } else if (mOtherUserConfigurationInternal != null
+                    && updatedConfiguration.getUserId()
+                    == mOtherUserConfigurationInternal.getUserId()) {
+                mOtherUserConfigurationInternal = updatedConfiguration;
+            }
             // Note: Unlike the real strategy, the listeners are invoked synchronously.
-            simulateConfigurationChangeForTests();
+            notifyConfigurationChange();
         }
         return true;
     }
 
-    void initializeConfiguration(ConfigurationInternal configurationInternal) {
-        mConfigurationInternal = configurationInternal;
+    void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
     }
 
-    void simulateConfigurationChangeForTests() {
-        for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
-            listener.onChange();
-        }
+    void initializeOtherUserConfiguration(ConfigurationInternal configurationInternal) {
+        mOtherUserConfigurationInternal = configurationInternal;
+    }
+
+    void simulateCurrentUserConfigurationInternalChange(
+            ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
+    }
+
+    void simulateOtherUserConfigurationInternalChange(ConfigurationInternal configurationInternal) {
+        mOtherUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
     }
 
     @Override
     public ConfigurationInternal getConfigurationInternal(int userId) {
-        assertEquals("Multi-user testing not supported currently",
-                userId, mConfigurationInternal.getUserId());
-        return mConfigurationInternal;
+        if (userId == mCurrentUserConfigurationInternal.getUserId()) {
+            return mCurrentUserConfigurationInternal;
+        } else if (mOtherUserConfigurationInternal != null
+                    && userId == mOtherUserConfigurationInternal.getUserId()) {
+            return mOtherUserConfigurationInternal;
+        }
+        throw new AssertionError("userId not known: " + userId);
     }
 
     @Override
-    public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
+    public void addLocationTimeZoneManagerConfigListener(StateChangeListener listener) {
         failUnimplemented();
     }
 
@@ -206,9 +231,14 @@
         failUnimplemented();
     }
 
+    private void notifyConfigurationChange() {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+            listener.onChange();
+        }
+    }
+
     @SuppressWarnings("UnusedReturnValue")
     private static <T> T failUnimplemented() {
-        fail("Unimplemented");
-        return null;
+        throw new AssertionError("Unimplemented");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 228dc95..fed8b40 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -15,23 +15,71 @@
  */
 package com.android.server.timezonedetector;
 
+import static org.junit.Assert.assertEquals;
+
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
 
+import java.util.ArrayList;
+
 public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
+    private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
+            new FakeServiceConfigAccessor();
+    private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
     private TimeZoneState mTimeZoneState;
 
+    public FakeTimeZoneDetectorStrategy() {
+        mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
+                this::notifyChangeListeners);
+    }
+
+    public void initializeConfiguration(ConfigurationInternal configuration) {
+        mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+    }
+
     @Override
     public boolean confirmTimeZone(String timeZoneId) {
         return false;
     }
 
     @Override
+    public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal =
+                mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        assertEquals("Multi-user testing not supported",
+                configurationInternal.getUserId(), userId);
+        return new TimeZoneCapabilitiesAndConfig(
+                configurationInternal.asCapabilities(bypassUserPolicyChecks),
+                configurationInternal.asConfiguration());
+    }
+
+    @Override
+    public boolean updateConfiguration(int userId, TimeZoneConfiguration requestedChanges,
+            boolean bypassUserPolicyChecks) {
+        return mFakeServiceConfigAccessor.updateConfiguration(
+                userId, requestedChanges, bypassUserPolicyChecks);
+    }
+
+    @Override
+    public void addChangeListener(StateChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    private void notifyChangeListeners() {
+        for (StateChangeListener listener : mListeners) {
+            listener.onChange();
+        }
+    }
+
+    @Override
     public TimeZoneState getTimeZoneState() {
         return mTimeZoneState;
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 782eebf..223c532 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -161,7 +161,8 @@
 
     private static ConfigurationInternal createConfigurationInternal(
             boolean enhancedMetricsCollectionEnabled) {
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
index 21c9685..eb6f00c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -66,10 +66,14 @@
     /**
      * Waits for all enqueued work to be completed before returning.
      */
-    public void waitForMessagesToBeProcessed() throws InterruptedException {
+    public void waitForMessagesToBeProcessed() {
         synchronized (mMonitor) {
             if (mMessagesSent != mMessagesProcessed) {
-                mMonitor.wait();
+                try {
+                    mMonitor.wait();
+                } catch (InterruptedException e) {
+                    throw new AssertionError("Unexpected exception", e);
+                }
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 276fdb9..8909832 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -50,7 +50,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
     private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +64,11 @@
         mTestHandler = new TestHandler(mHandlerThread.getLooper());
         mTestCurrentUserIdentityInjector = new TestCurrentUserIdentityInjector();
         mTestCurrentUserIdentityInjector.initializeCurrentUserId(ARBITRARY_USER_ID);
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
 
         mTimeZoneDetectorInternal = new TimeZoneDetectorInternalImpl(
                 mMockContext, mTestHandler, mTestCurrentUserIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+                mFakeTimeZoneDetectorStrategySpy);
     }
 
     @After
@@ -83,17 +81,20 @@
     public void testGetCapabilitiesAndConfigForDpm() throws Exception {
         final boolean autoDetectionEnabled = true;
         ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
 
         int expectedUserId = mTestCurrentUserIdentityInjector.getCurrentUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+        final boolean expectedBypassUserPolicyChecks = true;
+        verify(mFakeTimeZoneDetectorStrategySpy).getCapabilitiesAndConfig(
+                expectedUserId, expectedBypassUserPolicyChecks);
 
-        final boolean bypassUserPolicyChecks = true;
         TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
-                testConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+                new TimeZoneCapabilitiesAndConfig(
+                        testConfig.asCapabilities(expectedBypassUserPolicyChecks),
+                        testConfig.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -102,7 +103,7 @@
         final boolean autoDetectionEnabled = false;
         ConfigurationInternal initialConfigurationInternal =
                 createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
 
         TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
@@ -110,7 +111,7 @@
         assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
 
         final boolean expectedBypassUserPolicyChecks = true;
-        verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
+        verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
                 mTestCurrentUserIdentityInjector.getCurrentUserId(),
                 timeConfiguration,
                 expectedBypassUserPolicyChecks);
@@ -148,7 +149,8 @@
     }
 
     private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index bb9d564..d8346ee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -69,7 +69,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCallerIdentityInjector mTestCallerIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
 
@@ -85,12 +84,11 @@
         mTestCallerIdentityInjector = new TestCallerIdentityInjector();
         mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
 
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
 
         mTimeZoneDetectorService = new TimeZoneDetectorService(
                 mMockContext, mTestHandler, mTestCallerIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+                mFakeTimeZoneDetectorStrategySpy);
     }
 
     @After
@@ -115,7 +113,7 @@
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +122,14 @@
                 eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
 
         int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
-
         boolean expectedBypassUserPolicyChecks = false;
+        verify(mFakeTimeZoneDetectorStrategySpy)
+                .getCapabilitiesAndConfig(expectedUserId, expectedBypassUserPolicyChecks);
+
         TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
-                configuration.createCapabilitiesAndConfig(expectedBypassUserPolicyChecks);
+                new TimeZoneCapabilitiesAndConfig(
+                        configuration.asCapabilities(expectedBypassUserPolicyChecks),
+                        configuration.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -160,7 +161,7 @@
     public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -455,7 +456,8 @@
         // Default geo detection settings from auto detection settings - they are not important to
         // the tests.
         final boolean geoDetectionEnabled = autoDetectionEnabled;
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index d0a7c92..f50e7fb 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -38,19 +38,28 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+import android.os.HandlerThread;
 
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -97,7 +106,8 @@
     };
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -110,7 +120,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -123,7 +134,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
                     .setTelephonyFallbackSupported(false)
@@ -136,7 +148,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -149,7 +162,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -162,7 +176,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -174,14 +189,223 @@
                     .setGeoDetectionEnabledSetting(true)
                     .build();
 
-    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeEnvironment mFakeEnvironment;
+    private HandlerThread mHandlerThread;
+    private TestHandler mTestHandler;
+
+    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
 
     @Before
     public void setUp() {
         mFakeEnvironment = new FakeEnvironment();
-        mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
-        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
+        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+                CONFIG_AUTO_DISABLED_GEO_DISABLED);
+
+        // Create a thread + handler for processing the work that the strategy posts.
+        mHandlerThread = new HandlerThread("TimeZoneDetectorStrategyImplTest");
+        mHandlerThread.start();
+        mTestHandler = new TestHandler(mHandlerThread.getLooper());
+        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(
+                mFakeServiceConfigAccessorSpy, mTestHandler, mFakeEnvironment);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+    }
+
+    @Test
+    public void testChangeListenerBehavior_currentUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+        // The strategy initializes itself with the current user's config during construction.
+        assertEquals(currentUserConfig,
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        // Report a config change, but not one that actually changes anything.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_DISABLED_GEO_DISABLED);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(0);
+            assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Report a config change that actually changes something.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_ENABLED_GEO_ENABLED);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(1);
+            stateChangeListener.resetNotificationsReceivedCount();
+            assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Perform a (current user) update via the strategy.
+        {
+            TimeZoneConfiguration requestedChanges =
+                    new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    USER_ID, requestedChanges, bypassUserPolicyChecks);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(1);
+            stateChangeListener.resetNotificationsReceivedCount();
+        }
+    }
+
+    // Perform a (not current user) update via the strategy. There's no listener behavior for
+    // updates to "other" users.
+    @Test
+    public void testChangeListenerBehavior_otherUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+        // The strategy initializes itself with the current user's config during construction.
+        assertEquals(currentUserConfig,
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.initializeOtherUserConfiguration(otherUserConfig);
+
+        TimeZoneConfiguration requestedChanges =
+                new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+        mTimeZoneDetectorStrategy.updateConfiguration(
+                otherUserId, requestedChanges, bypassUserPolicyChecks);
+        mTestHandler.waitForMessagesToBeProcessed();
+
+        // Only changes to the current user's config are notified.
+        stateChangeListener.assertNotificationsReceived(0);
+        stateChangeListener.resetNotificationsReceivedCount();
+    }
+
+    // Current user behavior: the strategy caches and returns the latest configuration.
+    @Test
+    public void testReadAndWriteConfiguration_currentUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+        reset(mFakeServiceConfigAccessorSpy);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        ConfigurationInternal cachedConfigurationInternal =
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+        assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                            currentUserConfig.getUserId(), bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+                    currentUserConfig.getUserId());
+
+            assertEquals(currentUserConfig.asCapabilities(bypassUserPolicyChecks),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(currentUserConfig.asConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+        // the cached copy.
+        {
+            boolean newGeoDetectionEnabled =
+                    !cachedConfigurationInternal.asConfiguration().isGeoDetectionEnabled();
+            TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(newGeoDetectionEnabled)
+                    .build();
+            ConfigurationInternal expectedConfigAfterChange =
+                    new ConfigurationInternal.Builder(cachedConfigurationInternal)
+                            .setGeoDetectionEnabledSetting(newGeoDetectionEnabled)
+                            .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(expectedConfigAfterChange,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+    }
+
+    // Not current user behavior: the strategy reads from the ServiceConfigAccessor.
+    @Test
+    public void testReadAndWriteConfiguration_otherUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+        reset(mFakeServiceConfigAccessorSpy);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                            otherUserId, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).getConfigurationInternal(otherUserId);
+
+            assertEquals(otherUserConfig.asCapabilities(bypassUserPolicyChecks),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(otherUserConfig.asConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and doesn't
+        // touch the cached copy.
+        {
+            ConfigurationInternal cachedConfigBeforeChange =
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+            boolean newGeoDetectionEnabled =
+                    !otherUserConfig.asConfiguration().isGeoDetectionEnabled();
+            TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(newGeoDetectionEnabled)
+                    .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(cachedConfigBeforeChange,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
     }
 
     @Test
@@ -1201,19 +1425,13 @@
 
         private final TestState<String> mTimeZoneId = new TestState<>();
         private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
-        private ConfigurationInternal mConfigurationInternal;
         private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
-        private ConfigurationChangeListener mConfigurationInternalChangeListener;
 
         FakeEnvironment() {
             // Ensure the fake environment starts with the defaults a fresh device would.
             initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
         }
 
-        void initializeConfig(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-        }
-
         void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
@@ -1228,16 +1446,6 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
-            mConfigurationInternalChangeListener = listener;
-        }
-
-        @Override
-        public ConfigurationInternal getCurrentUserConfigurationInternal() {
-            return mConfigurationInternal;
-        }
-
-        @Override
         public String getDeviceTimeZone() {
             return mTimeZoneId.getLatest();
         }
@@ -1254,11 +1462,6 @@
             mTimeZoneConfidence.set(confidence);
         }
 
-        void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-            mConfigurationInternalChangeListener.onChange();
-        }
-
         void assertTimeZoneNotChanged() {
             mTimeZoneId.assertHasNotBeenSet();
             mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1322,7 +1525,8 @@
          * Simulates the user / user's configuration changing.
          */
         Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    configurationInternal);
             return this;
         }
 
@@ -1331,7 +1535,7 @@
          */
         Script simulateSetAutoMode(boolean autoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1547,7 @@
          */
         Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1457,4 +1661,22 @@
             @MatchType int matchType, @Quality int quality, int expectedScore) {
         return new TelephonyTestCase(matchType, quality, expectedScore);
     }
+
+    private static class TestStateChangeListener implements StateChangeListener {
+
+        private int mNotificationsReceived;
+
+        @Override
+        public void onChange() {
+            mNotificationsReceived++;
+        }
+
+        public void resetNotificationsReceivedCount() {
+            mNotificationsReceived = 0;
+        }
+
+        public void assertNotificationsReceived(int expectedCount) {
+            assertEquals(expectedCount, mNotificationsReceived);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
index 34d0082..f8d169b 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/FakeTimeZoneProviderEventPreProcessor.java
@@ -31,9 +31,9 @@
     @Override
     public TimeZoneProviderEvent preProcess(TimeZoneProviderEvent timeZoneProviderEvent) {
         if (mIsUncertain) {
+            TimeZoneProviderStatus timeZoneProviderStatus = null;
             return TimeZoneProviderEvent.createUncertainEvent(
-                    timeZoneProviderEvent.getCreationElapsedMillis(),
-                    TimeZoneProviderStatus.UNKNOWN);
+                    timeZoneProviderEvent.getCreationElapsedMillis(), timeZoneProviderStatus);
         }
         return timeZoneProviderEvent;
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index ed426cd..c18acd2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -16,10 +16,10 @@
 package com.android.server.timezonedetector.location;
 
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
 
 import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
@@ -87,9 +87,9 @@
             createSuggestionEvent(asList("Europe/Paris"));
     private static final TimeZoneProviderStatus UNCERTAIN_PROVIDER_STATUS =
             new TimeZoneProviderStatus.Builder()
-                    .setLocationDetectionStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
-                    .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                    .setTimeZoneResolutionStatus(OPERATION_STATUS_UNKNOWN)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE)
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN)
                     .build();
     private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
             TimeZoneProviderEvent.createUncertainEvent(
@@ -1405,9 +1405,9 @@
 
     private static TimeZoneProviderEvent createSuggestionEvent(@NonNull List<String> timeZoneIds) {
         TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
-                .setConnectivityStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
                 .setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 8429fa4..2bee7e6 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.timezonedetector.location;
 
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
@@ -57,6 +60,12 @@
 public class LocationTimeZoneProviderTest {
 
     private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 123456789L;
+    private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+            new TimeZoneProviderStatus.Builder()
+                    .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                    .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+                    .build();
 
     private TestThreadingDomain mTestThreadingDomain;
     private TestProviderListener mProviderListener;
@@ -80,91 +89,108 @@
                 mTimeZoneProviderEventPreProcessor);
 
         // initialize()
-        provider.initialize(mProviderListener);
-        provider.assertOnInitializeCalled();
+        {
+            provider.initialize(mProviderListener);
+            provider.assertOnInitializeCalled();
 
-        ProviderState currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
-        assertNull(currentState.currentUserConfiguration);
-        assertSame(provider, currentState.provider);
-        mTestThreadingDomain.assertQueueEmpty();
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STOPPED,
+                    /*expectedReportedStatus=*/null);
+            assertNull(currentState.currentUserConfiguration);
+            assertSame(provider, currentState.provider);
+            mTestThreadingDomain.assertQueueEmpty();
+        }
+
+        ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
 
         // startUpdates()
-        ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
-        Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
-        Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
-        Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
-        provider.startUpdates(config, arbitraryInitializationTimeout,
-                arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
+        {
+            Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+            Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+            Duration arbitraryEventFilteringAgeThreshold = Duration.ofMinutes(3);
+            provider.startUpdates(config, arbitraryInitializationTimeout,
+                    arbitraryInitializationTimeoutFuzz, arbitraryEventFilteringAgeThreshold);
 
-        provider.assertOnStartCalled(
-                arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
+            provider.assertOnStartCalled(
+                    arbitraryInitializationTimeout, arbitraryEventFilteringAgeThreshold);
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING);
-        assertSame(provider, currentState.provider);
-        assertEquals(config, currentState.currentUserConfiguration);
-        assertNull(currentState.event);
-        // The initialization timeout should be queued.
-        Duration expectedInitializationTimeout =
-                arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
-        mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
-        // We don't intend to trigger the timeout, so clear it.
-        mTestThreadingDomain.removeAllQueuedRunnables();
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STARTED_INITIALIZING,
+                    /*expectedReportedStatus=*/null);
+            assertSame(provider, currentState.provider);
+            assertEquals(config, currentState.currentUserConfiguration);
+            assertNull(currentState.event);
+            // The initialization timeout should be queued.
+            Duration expectedInitializationTimeout =
+                    arbitraryInitializationTimeout.plus(arbitraryInitializationTimeoutFuzz);
+            mTestThreadingDomain.assertSingleDelayedQueueItem(expectedInitializationTimeout);
+            // We don't intend to trigger the timeout, so clear it.
+            mTestThreadingDomain.removeAllQueuedRunnables();
 
-        // Entering started does not trigger an onProviderStateChanged() as it is requested by the
-        // controller.
-        mProviderListener.assertProviderChangeNotReported();
+            // Entering started does not trigger an onProviderStateChanged() as it is requested by
+            // the controller.
+            mProviderListener.assertProviderChangeNotReported();
+        }
 
         // Simulate a suggestion event being received.
-        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
-                .setTimeZoneIds(Arrays.asList("Europe/London"))
-                .build();
-        TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
-        TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
-                ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, providerStatus);
-        provider.simulateProviderEventReceived(event);
+        {
+            TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                    .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                    .setTimeZoneIds(Arrays.asList("Europe/London"))
+                    .build();
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, ARBITRARY_PROVIDER_STATUS);
+            provider.simulateProviderEventReceived(event);
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN);
-        assertSame(provider, currentState.provider);
-        assertEquals(event, currentState.event);
-        assertEquals(config, currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertQueueEmpty();
-        mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STARTED_CERTAIN,
+                    ARBITRARY_PROVIDER_STATUS);
+            assertSame(provider, currentState.provider);
+            assertEquals(event, currentState.event);
+            assertEquals(config, currentState.currentUserConfiguration);
+            mTestThreadingDomain.assertQueueEmpty();
+            mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_CERTAIN);
+        }
 
         // Simulate an uncertain event being received.
-        event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
-                TimeZoneProviderStatus.UNKNOWN);
-        provider.simulateProviderEventReceived(event);
+        {
+            TimeZoneProviderEvent event = TimeZoneProviderEvent.createUncertainEvent(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS, ARBITRARY_PROVIDER_STATUS);
+            provider.simulateProviderEventReceived(event);
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN);
-        assertSame(provider, currentState.provider);
-        assertEquals(event, currentState.event);
-        assertEquals(config, currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertQueueEmpty();
-        mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STARTED_UNCERTAIN,
+                    ARBITRARY_PROVIDER_STATUS);
+            assertSame(provider, currentState.provider);
+            assertEquals(event, currentState.event);
+            assertEquals(config, currentState.currentUserConfiguration);
+            mTestThreadingDomain.assertQueueEmpty();
+            mProviderListener.assertProviderChangeReported(PROVIDER_STATE_STARTED_UNCERTAIN);
+        }
 
         // stopUpdates()
-        provider.stopUpdates();
-        provider.assertOnStopUpdatesCalled();
+        {
+            provider.stopUpdates();
+            provider.assertOnStopUpdatesCalled();
 
-        currentState = assertAndReturnProviderState(
-                provider, providerMetricsLogger, PROVIDER_STATE_STOPPED);
-        assertSame(provider, currentState.provider);
-        assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
-        assertNull(currentState.event);
-        assertNull(currentState.currentUserConfiguration);
-        mTestThreadingDomain.assertQueueEmpty();
-        // Entering stopped does not trigger an onProviderStateChanged() as it is requested by the
-        // controller.
-        mProviderListener.assertProviderChangeNotReported();
+            ProviderState currentState = assertAndReturnProviderState(
+                    provider, providerMetricsLogger, PROVIDER_STATE_STOPPED,
+                    /*expectedReportedStatus=*/null);
+            assertSame(provider, currentState.provider);
+            assertEquals(PROVIDER_STATE_STOPPED, currentState.stateEnum);
+            assertNull(currentState.event);
+            assertNull(currentState.currentUserConfiguration);
+            mTestThreadingDomain.assertQueueEmpty();
+            // Entering stopped does not trigger an onProviderStateChanged() as it is requested by
+            // the controller.
+            mProviderListener.assertProviderChangeNotReported();
+        }
 
         // destroy()
-        provider.destroy();
-        provider.assertOnDestroyCalled();
+        {
+            provider.destroy();
+            provider.assertOnDestroyCalled();
+        }
     }
 
     @Test
@@ -196,13 +222,12 @@
                 .setTimeZoneIds(Arrays.asList("Europe/London"))
                 .build();
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
-                ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, TimeZoneProviderStatus.UNKNOWN);
+                ARBITRARY_ELAPSED_REALTIME_MILLIS, suggestion, null);
         provider.simulateProviderEventReceived(event);
         provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_CERTAIN);
 
         // Simulate an uncertain event being received.
-        event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS,
-                TimeZoneProviderStatus.UNKNOWN);
+        event = TimeZoneProviderEvent.createUncertainEvent(ARBITRARY_ELAPSED_REALTIME_MILLIS, null);
         provider.simulateProviderEventReceived(event);
         provider.assertLatestRecordedState(PROVIDER_STATE_STARTED_UNCERTAIN);
 
@@ -239,7 +264,7 @@
                 .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .setTimeZoneIds(invalidTimeZoneIds)
                 .build();
-        TimeZoneProviderStatus providerStatus = TimeZoneProviderStatus.UNKNOWN;
+        TimeZoneProviderStatus providerStatus = null;
         TimeZoneProviderEvent event = TimeZoneProviderEvent.createSuggestionEvent(
                 ARBITRARY_ELAPSED_REALTIME_MILLIS, invalidIdSuggestion, providerStatus);
         provider.simulateProviderEventReceived(event);
@@ -275,9 +300,11 @@
      */
     private static ProviderState assertAndReturnProviderState(
             TestLocationTimeZoneProvider provider,
-            RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum) {
+            RecordingProviderMetricsLogger providerMetricsLogger, int expectedStateEnum,
+            TimeZoneProviderStatus expectedReportedStatus) {
         ProviderState currentState = provider.getCurrentState();
         assertEquals(expectedStateEnum, currentState.stateEnum);
+        assertEquals(expectedReportedStatus, currentState.getReportedStatus());
         providerMetricsLogger.assertChangeLoggedAndRemove(expectedStateEnum);
         providerMetricsLogger.assertNoMoreLogEntries();
         return currentState;
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 2c3a7c4..042e3ef8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -42,7 +42,8 @@
 
     private static ConfigurationInternal createUserConfig(
             @UserIdInt int userId, boolean geoDetectionEnabledSetting) {
-        return new ConfigurationInternal.Builder(userId)
+        return new ConfigurationInternal.Builder()
+                .setUserId(userId)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index c478604..f3440f7 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.timezonedetector.location;
 
-import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
 import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED;
-import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_WORKING;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -61,7 +61,7 @@
 
             TimeZoneProviderStatus expectedProviderStatus =
                     new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
-                            .setTimeZoneResolutionStatus(OPERATION_STATUS_FAILED)
+                            .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
                             .build();
 
             TimeZoneProviderEvent expectedResultEvent =
@@ -75,9 +75,9 @@
 
     private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
         TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
-                .setLocationDetectionStatus(DEPENDENCY_STATUS_WORKING)
-                .setConnectivityStatus(DEPENDENCY_STATUS_WORKING)
-                .setTimeZoneResolutionStatus(OPERATION_STATUS_WORKING)
+                .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+                .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
                 .build();
         TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
                 .setTimeZoneIds(Arrays.asList(timeZoneIds))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2ed8b10..668345d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7605,43 +7605,6 @@
     }
 
     @Test
-    public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
-        mService.isSystemUid = true;
-        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
-        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
-                .thenReturn(true);
-        mService.setZenHelper(mockZenModeHelper);
-        ComponentName owner = new ComponentName("android", "ProviderName");
-        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
-        boolean isEnabled = true;
-        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
-                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
-        mBinderService.addAutomaticZenRule(rule, "com.android.settings");
-
-        // verify that zen mode helper gets passed in a package name of "android"
-        verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
-    }
-
-    @Test
-    public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
-        mService.isSystemUid = false;
-        ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
-        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
-                .thenReturn(true);
-        mService.setZenHelper(mockZenModeHelper);
-        ComponentName owner = new ComponentName("android", "ProviderName");
-        ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
-        boolean isEnabled = true;
-        AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
-                zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
-        mBinderService.addAutomaticZenRule(rule, "another.package");
-
-        // verify that zen mode helper gets passed in the package name from the arg, not the owner
-        verify(mockZenModeHelper).addAutomaticZenRule(
-                eq("another.package"), eq(rule), anyString());
-    }
-
-    @Test
     public void testAreNotificationsEnabledForPackage() throws Exception {
         mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
                 mUid);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 49edde5..ba61980 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1672,36 +1672,6 @@
     }
 
     @Test
-    public void testAddAutomaticZenRule_claimedSystemOwner() {
-        // Make sure anything that claims to have a "system" owner but not actually part of the
-        // system package still gets limited on number of rules
-        for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
-            ScheduleInfo si = new ScheduleInfo();
-            si.startHour = i;
-            AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
-                    new ComponentName("android", "ScheduleConditionProvider" + i),
-                    null, // configuration activity
-                    ZenModeConfig.toScheduleConditionId(si),
-                    new ZenPolicy.Builder().build(),
-                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
-            assertNotNull(id);
-        }
-        try {
-            AutomaticZenRule zenRule = new AutomaticZenRule("name",
-                    new ComponentName("android", "ScheduleConditionProviderFinal"),
-                    null, // configuration activity
-                    ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
-                    new ZenPolicy.Builder().build(),
-                    NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
-            String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
-            fail("allowed too many rules to be created");
-        } catch (IllegalArgumentException e) {
-            // yay
-        }
-    }
-
-    @Test
     public void testAddAutomaticZenRule_CA() {
         AutomaticZenRule zenRule = new AutomaticZenRule("name",
                 null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4ec9762..3a8e1cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2884,6 +2884,7 @@
         fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
         task.addChild(taskFragment1, POSITION_TOP);
         assertEquals(task, activity1.mStartingData.mAssociatedTask);
+        assertEquals(activity1.mStartingData, task.mSharedStartingData);
 
         final TaskFragment taskFragment2 = new TaskFragment(
                 mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2903,7 +2904,6 @@
 
         verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
                 eq(task.mSurfaceControl));
-        assertEquals(activity1.mStartingData, startingWindow.mStartingData);
         assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
         assertEquals(taskFragment1.getBounds(), activity1.getBounds());
         // The activity was resized by task fragment, but starting window must still cover the task.
@@ -2914,6 +2914,7 @@
         activity1.onFirstWindowDrawn(activityWindow);
         activity2.onFirstWindowDrawn(activityWindow);
         assertNull(activity1.mStartingWindow);
+        assertNull(task.mSharedStartingData);
     }
 
     @Test
@@ -2989,10 +2990,10 @@
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
         final TestWindowState startingWindow = createWindowState(attrs, activity);
-        activity.startingDisplayed = true;
+        activity.mStartingData = mock(StartingData.class);
         activity.addWindow(startingWindow);
         assertTrue("Starting window should be present", activity.hasStartingWindow());
-        activity.startingDisplayed = false;
+        activity.mStartingData = null;
         assertTrue("Starting window should be present", activity.hasStartingWindow());
 
         activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 2b0e76c..053718e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -98,6 +98,7 @@
 import android.util.Pair;
 import android.util.Size;
 import android.view.Gravity;
+import android.view.RemoteAnimationAdapter;
 import android.window.TaskFragmentOrganizerToken;
 
 import androidx.test.filters.SmallTest;
@@ -1323,6 +1324,32 @@
     }
 
     @Test
+    public void testRemoteAnimation_appliesToExistingTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+
+        // Put an activity on default display as the top focused activity.
+        ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Intent intent = new Intent();
+        intent.setComponent(ActivityBuilder.getDefaultComponent());
+        starter.setReason("testRemoteAnimation_newTask")
+                .setIntent(intent)
+                .execute();
+
+        assertNull(mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+
+        // Relaunch the activity with remote animation indicated in options.
+        final RemoteAnimationAdapter adaptor = mock(RemoteAnimationAdapter.class);
+        final ActivityOptions options = ActivityOptions.makeRemoteAnimation(adaptor);
+        starter.setReason("testRemoteAnimation_existingTask")
+                .setIntent(intent)
+                .setActivityOptions(options.toBundle())
+                .execute();
+
+        // Verify the remote animation is updated.
+        assertEquals(adaptor, mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+    }
+
+    @Test
     public void testStartLaunchIntoPipActivity() {
         final ActivityStarter starter = prepareStarter(0, false);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
         activity.allDrawn = true;
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
+        // Assume the activity contains a window.
+        doReturn(true).when(activity).hasChild();
         // Make sure activity can create remote animation target.
         doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
                 any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa..32c95fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -27,7 +25,6 @@
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -321,7 +318,6 @@
         final ActivityRecord activity2 = createActivityRecord(dc2);
 
         activity1.allDrawn = true;
-        activity1.startingDisplayed = true;
         activity1.startingMoved = true;
 
         // Simulate activity resume / finish flows to prepare app transition & set visibility,
@@ -412,50 +408,38 @@
     }
 
     @Test
-    public void testExcludeLauncher() {
+    public void testDelayWhileRecents() {
         final DisplayContent dc = createNewDisplay(Display.STATE_ON);
         doReturn(false).when(dc).onDescendantOrientationChanged(any());
         final Task task = createTask(dc);
 
-        // Simulate activity1 launches activity2
+        // Simulate activity1 launches activity2.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
         activity1.mVisibleRequested = false;
         activity1.allDrawn = true;
-        dc.mClosingApps.add(activity1);
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
         activity2.mVisibleRequested = true;
         activity2.allDrawn = true;
+
+        dc.mClosingApps.add(activity1);
         dc.mOpeningApps.add(activity2);
         dc.prepareAppTransition(TRANSIT_OPEN);
-
-        // Simulate start recents
-        final ActivityRecord homeActivity = createActivityRecord(dc, WINDOWING_MODE_FULLSCREEN,
-                ACTIVITY_TYPE_HOME);
-        homeActivity.setVisible(false);
-        homeActivity.mVisibleRequested = true;
-        homeActivity.allDrawn = true;
-        dc.mOpeningApps.add(homeActivity);
-        dc.prepareAppTransition(TRANSIT_NONE);
-        doReturn(true).when(task)
-                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
 
         // Wait until everything in animation handler get executed to prevent the exiting window
         // from being removed during WindowSurfacePlacer Traversal.
         waitUntilHandlersIdle();
 
+        // Start recents
+        doReturn(true).when(task)
+                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+
         dc.mAppTransitionController.handleAppTransitionReady();
 
-        verify(activity1).commitVisibility(eq(false), anyBoolean(), anyBoolean());
-        verify(activity1).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(false),
-                anyBoolean(), any());
-        verify(activity2).commitVisibility(eq(true), anyBoolean(), anyBoolean());
-        verify(activity2).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(true),
-                anyBoolean(), any());
-        verify(homeActivity).commitVisibility(eq(true), anyBoolean(), anyBoolean());
-        verify(homeActivity, never()).applyAnimation(any(), anyInt(), anyBoolean(), anyBoolean(),
-                any());
+        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index bc319db..f3f56e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -98,28 +98,31 @@
     @Test
     public void backTypeCrossTaskWhenBackToPreviousTask() {
         Task taskA = createTask(mDefaultDisplay);
-        createActivityRecord(taskA);
+        ActivityRecord recordA = createActivityRecord(taskA);
+        Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
         withSystemCallback(createTopTaskWithActivity());
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
+
+        // verify if back animation would start.
+        verify(mBackNavigationController).scheduleAnimationLocked(
+                eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
+                any());
     }
 
     @Test
     public void backTypeCrossActivityWhenBackToPreviousActivity() {
-        Task task = createTopTaskWithActivity();
-        WindowState window = createAppWindow(task, FIRST_APPLICATION_WINDOW, "window");
-        addToWindowMap(window, true);
-        IOnBackInvokedCallback callback = createOnBackInvokedCallback();
-        window.setOnBackInvokedCallbackInfo(
-                new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM));
+        CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
+        IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
+
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+        assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
-        assertWithMessage("Activity callback").that(
-                backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
     }
 
     @Test
@@ -295,6 +298,34 @@
         return task;
     }
 
+    @NonNull
+    private CrossActivityTestCase createTopTaskWithTwoActivities() {
+        Task task = createTask(mDefaultDisplay);
+        ActivityRecord record1 = createActivityRecord(task);
+        ActivityRecord record2 = createActivityRecord(task);
+        // enable OnBackInvokedCallbacks
+        record2.info.applicationInfo.privateFlagsExt |=
+                PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
+        WindowState window1 = createWindow(null, FIRST_APPLICATION_WINDOW, record1, "window1");
+        WindowState window2 = createWindow(null, FIRST_APPLICATION_WINDOW, record2, "window2");
+        when(task.mSurfaceControl.isValid()).thenReturn(true);
+        when(record1.mSurfaceControl.isValid()).thenReturn(true);
+        when(record2.mSurfaceControl.isValid()).thenReturn(true);
+        Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
+        Mockito.doNothing().when(record1).reparentSurfaceControl(any(), any());
+        Mockito.doNothing().when(record2).reparentSurfaceControl(any(), any());
+        mAtm.setFocusedTask(task.mTaskId, record1);
+        mAtm.setFocusedTask(task.mTaskId, record2);
+        addToWindowMap(window1, true);
+        addToWindowMap(window2, true);
+
+        CrossActivityTestCase testCase = new CrossActivityTestCase();
+        testCase.task = task;
+        testCase.recordBack = record1;
+        testCase.recordFront = record2;
+        return testCase;
+    }
+
     private void addToWindowMap(WindowState window, boolean focus) {
         mWm.mWindowMap.put(window.mClient.asBinder(), window);
         if (focus) {
@@ -303,4 +334,10 @@
             doReturn(window).when(mWm).getFocusedWindowLocked();
         }
     }
+
+    private class CrossActivityTestCase {
+        public Task task;
+        public ActivityRecord recordBack;
+        public ActivityRecord recordFront;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index b1acae2..eab2e15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -42,7 +42,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Build/Install/Run:
@@ -66,55 +65,6 @@
     }
 
     @Test
-    public void testCollectTasksByLastActiveTime() {
-        // Create a number of stacks with tasks (of incrementing active time)
-        final ArrayList<DisplayContent> displays = new ArrayList<>();
-        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
-        displays.add(display);
-
-        final int numStacks = 2;
-        for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
-            final Task stack = new TaskBuilder(mSupervisor)
-                    .setDisplay(display)
-                    .setOnTop(false)
-                    .build();
-        }
-
-        final int numTasks = 10;
-        int activeTime = 0;
-        final List<Task> rootTasks = new ArrayList<>();
-        display.getDefaultTaskDisplayArea().forAllRootTasks(task -> {
-            rootTasks.add(task);
-        }, false /* traverseTopToBottom */);
-        for (int i = 0; i < numTasks; i++) {
-            final Task task =
-                    createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null);
-            doReturn(false).when(task).isVisible();
-        }
-
-        // Ensure that the latest tasks were returned in order of decreasing last active time,
-        // collected from all tasks across all the stacks
-        final int numFetchTasks = 5;
-        ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
-        mRunningTasks.getTasks(5, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
-                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
-        assertThat(tasks).hasSize(numFetchTasks);
-        for (int i = 0; i < numFetchTasks; i++) {
-            assertEquals(numTasks - i - 1, tasks.get(i).id);
-        }
-
-        // Ensure that requesting more than the total number of tasks only returns the subset
-        // and does not crash
-        tasks.clear();
-        mRunningTasks.getTasks(100, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
-                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
-        assertThat(tasks).hasSize(numTasks);
-        for (int i = 0; i < numTasks; i++) {
-            assertEquals(numTasks - i - 1, tasks.get(i).id);
-        }
-    }
-
-    @Test
     public void testTaskInfo_expectNoExtrasByDefault() {
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
         final int numTasks = 10;
@@ -125,7 +75,7 @@
                     .build();
             final Bundle data = new Bundle();
             data.putInt("key", 100);
-            createTask(stack, ".Task" + i, i, i, data);
+            createTask(stack, ".Task" + i, i, data);
         }
 
         final int numFetchTasks = 5;
@@ -150,7 +100,7 @@
                     .build();
             final Bundle data = new Bundle();
             data.putInt("key", 100);
-            createTask(stack, ".Task" + i, i, i, data);
+            createTask(stack, ".Task" + i, i, data);
         }
 
         final int numFetchTasks = 5;
@@ -167,46 +117,63 @@
     }
 
     @Test
-    public void testUpdateLastActiveTimeOfVisibleTasks() {
+    public void testGetTasksSortByFocusAndVisibility() {
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
+        final Task stack = new TaskBuilder(mSupervisor)
+                .setDisplay(display)
+                .setOnTop(true)
+                .build();
+
         final int numTasks = 10;
         final ArrayList<Task> tasks = new ArrayList<>();
         for (int i = 0; i < numTasks; i++) {
-            final Task task = createTask(null, ".Task" + i, i, i, null);
+            final Task task = createTask(stack, ".Task" + i, i, null);
             doReturn(false).when(task).isVisible();
             tasks.add(task);
         }
 
-        final Task visibleTask = tasks.get(0);
-        doReturn(true).when(visibleTask).isVisible();
-
-        final Task focusedTask = tasks.get(1);
+        final Task focusedTask = tasks.get(numTasks - 1);
         doReturn(true).when(focusedTask).isVisible();
-        doReturn(true).when(focusedTask).isFocused();
+        display.mFocusedApp = focusedTask.getTopNonFinishingActivity();
 
-        // Ensure that the last active time of visible tasks were updated while the focused one had
-        // the largest last active time.
+        final Task visibleTaskTop = tasks.get(numTasks - 2);
+        doReturn(true).when(visibleTaskTop).isVisible();
+
+        final Task visibleTaskBottom = tasks.get(numTasks - 3);
+        doReturn(true).when(visibleTaskBottom).isVisible();
+
+        // Ensure that the focused Task is on top, visible tasks below, then invisible tasks.
         final int numFetchTasks = 5;
         final ArrayList<RunningTaskInfo> fetchTasks = new ArrayList<>();
         mRunningTasks.getTasks(numFetchTasks, fetchTasks,
                 FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
                 mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
         assertThat(fetchTasks).hasSize(numFetchTasks);
-        assertEquals(fetchTasks.get(0).id, focusedTask.mTaskId);
-        assertEquals(fetchTasks.get(1).id, visibleTask.mTaskId);
+        for (int i = 0; i < numFetchTasks; i++) {
+            assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+        }
+
+        // Ensure that requesting more than the total number of tasks only returns the subset
+        // and does not crash
+        fetchTasks.clear();
+        mRunningTasks.getTasks(100, fetchTasks,
+                FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
+                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
+        assertThat(fetchTasks).hasSize(numTasks);
+        for (int i = 0; i < numTasks; i++) {
+            assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+        }
     }
 
     /**
-     * Create a task with a single activity in it, with the given last active time.
+     * Create a task with a single activity in it.
      */
-    private Task createTask(Task stack, String className, int taskId,
-            int lastActiveTime, Bundle extras) {
+    private Task createTask(Task stack, String className, int taskId, Bundle extras) {
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
                 .setComponent(new ComponentName(mContext.getPackageName(), className))
                 .setTaskId(taskId)
                 .setParentTaskFragment(stack)
                 .build();
-        task.lastActiveTime = lastActiveTime;
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(task)
                 .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
@@ -227,7 +194,7 @@
                     .setDisplay(i % 2 == 0 ? display0 : display1)
                     .setOnTop(true)
                     .build();
-            final Task task = createTask(stack, ".Task" + i, i, i, null);
+            final Task task = createTask(stack, ".Task" + i, i, null);
             tasks.add(task);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d59fce0..c906abc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,7 +87,6 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.view.InsetsFrameProvider;
-import android.view.InsetsVisibilities;
 import android.view.WindowManager;
 
 import androidx.test.filters.MediumTest;
@@ -2302,8 +2301,7 @@
         // We should get a null LetterboxDetails object as there is no letterboxed activity, so
         // nothing will get passed to SysUI
         verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), isNull());
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), isNull());
 
     }
 
@@ -2331,8 +2329,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
         verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     @Test
@@ -2367,8 +2364,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
         verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     @Test
@@ -2420,8 +2416,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
         verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     private void recomputeNaturalConfigurationOfUnresizableActivity() {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java
deleted file mode 100644
index 73b4ce7..0000000
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.soundtrigger;
-
-import android.util.Log;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.LinkedList;
-
-/**
-* Constructor SoundTriggerLogger class
-*/
-public class SoundTriggerLogger {
-
-    // ring buffer of events to log.
-    private final LinkedList<Event> mEvents;
-
-    private final String mTitle;
-
-    // the maximum number of events to keep in log
-    private final int mMemSize;
-
-    /**
-     * Constructor for Event class.
-     */
-    public abstract static class Event {
-        // formatter for timestamps
-        private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
-
-        private final long mTimestamp;
-
-        Event() {
-            mTimestamp = System.currentTimeMillis();
-        }
-
-    /**
-     * Convert event to String
-     * @return StringBuilder
-     */
-        public String toString() {
-            return (new StringBuilder(sFormat.format(new Date(mTimestamp))))
-                    .append(" ").append(eventToString()).toString();
-        }
-
-        /**
-         * Causes the string message for the event to appear in the logcat.
-         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
-         * (an instance of SoundTriggerLogger) while also making it show in the logcat:
-         * <pre>
-         *     myLogger.log(
-         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
-         * </pre>
-         * @param tag the tag for the android.util.Log.v
-         * @return the same instance of the event
-         */
-        public Event printLog(String tag) {
-            Log.i(tag, eventToString());
-            return this;
-        }
-
-        /**
-         * Convert event to String.
-         * This method is only called when the logger history is about to the dumped,
-         * so this method is where expensive String conversions should be made, not when the Event
-         * subclass is created.
-         * Timestamp information will be automatically added, do not include it.
-         * @return a string representation of the event that occurred.
-         */
-        public abstract String eventToString();
-    }
-
-    /**
-    * Constructor StringEvent class
-    */
-    public static class StringEvent extends Event {
-        private final String mMsg;
-
-        public StringEvent(String msg) {
-            mMsg = msg;
-        }
-
-        @Override
-        public String eventToString() {
-            return mMsg;
-        }
-    }
-
-    /**
-     * Constructor for logger.
-     * @param size the maximum number of events to keep in log
-     * @param title the string displayed before the recorded log
-     */
-    public SoundTriggerLogger(int size, String title) {
-        mEvents = new LinkedList<Event>();
-        mMemSize = size;
-        mTitle = title;
-    }
-
-    /**
-     * Constructor for logger.
-     * @param evt the maximum number of events to keep in log
-     */
-    public synchronized void log(Event evt) {
-        if (mEvents.size() >= mMemSize) {
-            mEvents.removeFirst();
-        }
-        mEvents.add(evt);
-    }
-
-    /**
-     * Constructor for logger.
-     * @param pw the maximum number of events to keep in log
-     */
-    public synchronized void dump(PrintWriter pw) {
-        pw.println("ST Event log: " + mTitle);
-        for (Event evt : mEvents) {
-            pw.println(evt.toString());
-        }
-    }
-}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 5183e5b..81717f4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -84,6 +84,7 @@
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
 import com.android.server.SystemService;
+import com.android.server.utils.EventLogger;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -309,14 +310,14 @@
                     Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : "
-                        + parcelUuid));
+                sEventLogger.enqueue(new EventLogger.StringEvent(
+                        "startRecognition(): Uuid : " + parcelUuid));
 
                 GenericSoundModel model = getSoundModel(parcelUuid);
                 if (model == null) {
                     Slog.w(TAG, "Null model in database for id: " + parcelUuid);
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "startRecognition(): Null model in database for id: " + parcelUuid));
 
                     return STATUS_ERROR;
@@ -339,7 +340,7 @@
                     Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
+                sEventLogger.enqueue(new EventLogger.StringEvent("stopRecognition(): Uuid : "
                         + parcelUuid));
 
                 int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(),
@@ -359,7 +360,7 @@
                     Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("getSoundModel(): id = "
                         + soundModelId));
 
                 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
@@ -376,7 +377,7 @@
                     Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("updateSoundModel(): model = "
                         + soundModel));
 
                 mDbHelper.updateGenericSoundModel(soundModel);
@@ -391,7 +392,7 @@
                     Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("deleteSoundModel(): id = "
                         + soundModelId));
 
                 // Unload the model if it is loaded.
@@ -411,7 +412,7 @@
                 if (soundModel == null || soundModel.getUuid() == null) {
                     Slog.w(TAG, "Invalid sound model");
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "loadGenericSoundModel(): Invalid sound model"));
 
                     return STATUS_ERROR;
@@ -420,7 +421,7 @@
                     Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid());
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("loadGenericSoundModel(): id = "
                         + soundModel.getUuid()));
 
                 synchronized (mLock) {
@@ -447,7 +448,7 @@
                 if (soundModel == null || soundModel.getUuid() == null) {
                     Slog.w(TAG, "Invalid sound model");
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "loadKeyphraseSoundModel(): Invalid sound model"));
 
                     return STATUS_ERROR;
@@ -455,7 +456,7 @@
                 if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) {
                     Slog.w(TAG, "Only one keyphrase per model is currently supported.");
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "loadKeyphraseSoundModel(): Only one keyphrase per model"
                                     + " is currently supported."));
 
@@ -465,8 +466,8 @@
                     Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid());
                 }
 
-                sEventLogger.log(
-                        new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = "
+                sEventLogger.enqueue(
+                        new EventLogger.StringEvent("loadKeyphraseSoundModel(): id = "
                                 + soundModel.getUuid()));
 
                 synchronized (mLock) {
@@ -503,7 +504,7 @@
                     Slog.i(TAG, "startRecognition(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "startRecognitionForService(): id = " + soundModelId));
 
                 IRecognitionStatusCallback callback =
@@ -515,7 +516,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "startRecognitionForService():" + soundModelId + " is not loaded"));
 
                         return STATUS_ERROR;
@@ -527,7 +528,7 @@
                     if (existingCallback != null) {
                         Slog.w(TAG, soundModelId + " is already running");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "startRecognitionForService():"
                                         + soundModelId + " is already running"));
 
@@ -542,7 +543,7 @@
                         default:
                             Slog.e(TAG, "Unknown model type");
 
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "startRecognitionForService(): Unknown model type"));
 
                             return STATUS_ERROR;
@@ -551,7 +552,7 @@
                     if (ret != STATUS_OK) {
                         Slog.e(TAG, "Failed to start model: " + ret);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "startRecognitionForService(): Failed to start model:"));
 
                         return ret;
@@ -574,7 +575,7 @@
                     Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "stopRecognitionForService(): id = " + soundModelId));
 
                 synchronized (mLock) {
@@ -582,7 +583,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "stopRecognitionForService(): " + soundModelId
                                         + " is not loaded"));
 
@@ -595,7 +596,7 @@
                     if (callback == null) {
                         Slog.w(TAG, soundModelId + " is not running");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "stopRecognitionForService(): " + soundModelId
                                         + " is not running"));
 
@@ -610,7 +611,7 @@
                         default:
                             Slog.e(TAG, "Unknown model type");
 
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "stopRecognitionForService(): Unknown model type"));
 
                             return STATUS_ERROR;
@@ -619,7 +620,7 @@
                     if (ret != STATUS_OK) {
                         Slog.e(TAG, "Failed to stop model: " + ret);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "stopRecognitionForService(): Failed to stop model: " + ret));
 
                         return ret;
@@ -642,7 +643,7 @@
                     Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("unloadSoundModel(): id = "
                         + soundModelId));
 
                 synchronized (mLock) {
@@ -650,7 +651,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "unloadSoundModel(): " + soundModelId + " is not loaded"));
 
                         return STATUS_ERROR;
@@ -667,7 +668,7 @@
                         default:
                             Slog.e(TAG, "Unknown model type");
 
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "unloadSoundModel(): Unknown model type"));
 
                             return STATUS_ERROR;
@@ -675,7 +676,7 @@
                     if (ret != STATUS_OK) {
                         Slog.e(TAG, "Failed to unload model");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "unloadSoundModel(): Failed to unload model"));
 
                         return ret;
@@ -709,7 +710,7 @@
                     Slog.i(TAG, "getModelState(): id = " + soundModelId);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = "
+                sEventLogger.enqueue(new EventLogger.StringEvent("getModelState(): id = "
                         + soundModelId));
 
                 synchronized (mLock) {
@@ -717,7 +718,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): "
+                        sEventLogger.enqueue(new EventLogger.StringEvent("getModelState(): "
                                 + soundModelId + " is not loaded"));
 
                         return ret;
@@ -729,7 +730,7 @@
                         default:
                             // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy.
                             Slog.e(TAG, "Unsupported model type, " + soundModel.getType());
-                            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                            sEventLogger.enqueue(new EventLogger.StringEvent(
                                     "getModelState(): Unsupported model type, "
                                             + soundModel.getType()));
                             break;
@@ -751,7 +752,7 @@
 
                 synchronized (mLock) {
                     ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                    sEventLogger.enqueue(new EventLogger.StringEvent(
                             "getModuleProperties(): " + properties));
                     return properties;
                 }
@@ -769,7 +770,7 @@
                             + ", value=" + value);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "setParameter(): id=" + soundModelId
                                 + ", param=" + modelParam
                                 + ", value=" + value));
@@ -780,7 +781,7 @@
                         Slog.w(TAG, soundModelId + " is not loaded. Loaded models: "
                                 + mLoadedModels.toString());
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
+                        sEventLogger.enqueue(new EventLogger.StringEvent("setParameter(): "
                                 + soundModelId + " is not loaded"));
 
                         return STATUS_BAD_VALUE;
@@ -803,7 +804,7 @@
                             + ", param=" + modelParam);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "getParameter(): id=" + soundModelId
                                 + ", param=" + modelParam));
 
@@ -812,7 +813,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
+                        sEventLogger.enqueue(new EventLogger.StringEvent("getParameter(): "
                                 + soundModelId + " is not loaded"));
 
                         throw new IllegalArgumentException("sound model is not loaded");
@@ -834,7 +835,7 @@
                             + ", param=" + modelParam);
                 }
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "queryParameter(): id=" + soundModelId
                                 + ", param=" + modelParam));
 
@@ -843,7 +844,7 @@
                     if (soundModel == null) {
                         Slog.w(TAG, soundModelId + " is not loaded");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "queryParameter(): "
                                         + soundModelId + " is not loaded"));
 
@@ -857,7 +858,7 @@
 
         private void clientDied() {
             Slog.w(TAG, "Client died, cleaning up session.");
-            sEventLogger.log(new SoundTriggerLogger.StringEvent(
+            sEventLogger.enqueue(new EventLogger.StringEvent(
                     "Client died, cleaning up session."));
             mSoundTriggerHelper.detach();
         }
@@ -1027,7 +1028,7 @@
                     } catch (Exception e) {
                         Slog.e(TAG, mPuuid + ": Cannot remove client", e);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": Cannot remove client"));
 
                     }
@@ -1052,7 +1053,7 @@
             private void destroy() {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + ": destroy"));
 
                 synchronized (mRemoteServiceLock) {
                     disconnectLocked();
@@ -1086,7 +1087,7 @@
                                 Slog.e(TAG, mPuuid + ": Could not stop operation "
                                         + mRunningOpIds.valueAt(i), e);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not stop operation " + mRunningOpIds.valueAt(
                                         i)));
 
@@ -1116,7 +1117,7 @@
                     if (ri == null) {
                         Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": " + mServiceName + " not found"));
 
                         return;
@@ -1127,7 +1128,7 @@
                         Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
                                 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": " + mServiceName + " does not require "
                                 + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
 
@@ -1143,7 +1144,7 @@
                     } else {
                         Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ": Could not bind to " + mServiceName));
 
                     }
@@ -1165,7 +1166,7 @@
                                 mPuuid + ": Dropped operation as already destroyed or marked for "
                                         + "destruction");
 
-                        sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                        sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                 + ":Dropped operation as already destroyed or marked for "
                                 + "destruction"));
 
@@ -1197,7 +1198,7 @@
                                             mPuuid + ": Dropped operation as too many operations "
                                                     + "were run in last 24 hours");
 
-                                    sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                    sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                             + ": Dropped operation as too many operations "
                                             + "were run in last 24 hours"));
 
@@ -1207,7 +1208,7 @@
                             } catch (Exception e) {
                                 Slog.e(TAG, mPuuid + ": Could not drop operation", e);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not drop operation"));
 
                             }
@@ -1224,7 +1225,7 @@
                             try {
                                 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": runOp " + opId));
 
                                 op.run(opId, mService);
@@ -1232,7 +1233,7 @@
                             } catch (Exception e) {
                                 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
 
-                                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                                         + ": Could not run operation " + opId));
 
                             }
@@ -1265,7 +1266,7 @@
                 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
                         + ")");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid + "->" + mServiceName
                         + ": IGNORED onKeyphraseDetected(" + event + ")"));
             }
 
@@ -1282,9 +1283,9 @@
                 attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
                 AudioAttributes attributes = attributesBuilder.build();
 
-                    AudioFormat originalFormat = event.getCaptureFormat();
+                AudioFormat originalFormat = event.getCaptureFormat();
 
-                    sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
+                sEventLogger.enqueue(new EventLogger.StringEvent("createAudioRecordForEvent"));
 
                 return (new AudioRecord.Builder())
                             .setAudioAttributes(attributes)
@@ -1301,7 +1302,7 @@
             public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": Generic sound trigger event: " + event));
 
                 runOrAddOperation(new Operation(
@@ -1336,7 +1337,7 @@
             public void onError(int status) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onError: " + status));
 
                 runOrAddOperation(
@@ -1359,7 +1360,7 @@
             public void onRecognitionPaused() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
 
             }
@@ -1368,7 +1369,7 @@
             public void onRecognitionResumed() {
                 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
 
             }
@@ -1377,7 +1378,7 @@
             public void onServiceConnected(ComponentName name, IBinder service) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onServiceConnected(" + service + ")"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1400,7 +1401,7 @@
             public void onServiceDisconnected(ComponentName name) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onServiceDisconnected"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1412,7 +1413,7 @@
             public void onBindingDied(ComponentName name) {
                 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+                sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
                         + ": onBindingDied"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1424,7 +1425,7 @@
             public void onNullBinding(ComponentName name) {
                 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
 
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
+                sEventLogger.enqueue(new EventLogger.StringEvent(name + " for model "
                         + mPuuid + " returned a null binding"));
 
                 synchronized (mRemoteServiceLock) {
@@ -1610,7 +1611,7 @@
 
             private void clientDied() {
                 Slog.w(TAG, "Client died, cleaning up session.");
-                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "Client died, cleaning up session."));
                 mSoundTriggerHelper.detach();
             }
@@ -1637,7 +1638,7 @@
     //=================================================================
     // For logging
 
-    private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200,
+    private static final EventLogger sEventLogger = new EventLogger(200,
             "SoundTrigger activity");
 
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
new file mode 100644
index 0000000..f921118
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.voiceinteraction;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+
+import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.media.permission.Identity;
+import android.os.ParcelFileDescriptor;
+import android.service.voice.HotwordAudioStream;
+import android.service.voice.HotwordDetectedResult;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+final class HotwordAudioStreamManager {
+
+    private static final String TAG = "HotwordAudioStreamManager";
+    private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService";
+    private static final String TASK_ID_PREFIX = "HotwordDetectedResult@";
+    private static final String THREAD_NAME_PREFIX = "Copy-";
+
+    private final AppOpsManager mAppOpsManager;
+    private final Identity mVoiceInteractorIdentity;
+    private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+    HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager,
+            @NonNull Identity voiceInteractorIdentity) {
+        mAppOpsManager = appOpsManager;
+        mVoiceInteractorIdentity = voiceInteractorIdentity;
+    }
+
+    /**
+     * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
+     * <p>
+     * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
+     * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
+     * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamManager}. The
+     * returned value should be passed on to the client (i.e., the voice interactor).
+     * </p>
+     *
+     * @throws IOException If there was an error creating the managed pipe.
+     */
+    @NonNull
+    public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
+            throws IOException {
+        List<HotwordAudioStream> audioStreams = result.getAudioStreams();
+        if (audioStreams.isEmpty()) {
+            return result;
+        }
+
+        List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size());
+        List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks = new ArrayList<>(
+                audioStreams.size());
+        for (HotwordAudioStream audioStream : audioStreams) {
+            ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe();
+            ParcelFileDescriptor clientAudioSource = clientPipe[0];
+            ParcelFileDescriptor clientAudioSink = clientPipe[1];
+            HotwordAudioStream newAudioStream =
+                    audioStream.buildUpon().setAudioStreamParcelFileDescriptor(
+                            clientAudioSource).build();
+            newAudioStreams.add(newAudioStream);
+
+            ParcelFileDescriptor serviceAudioSource =
+                    audioStream.getAudioStreamParcelFileDescriptor();
+            sourcesAndSinks.add(new Pair<>(serviceAudioSource, clientAudioSink));
+        }
+
+        String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
+        mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, sourcesAndSinks));
+
+        return result.buildUpon().setAudioStreams(newAudioStreams).build();
+    }
+
+    private class HotwordDetectedResultCopyTask implements Runnable {
+        private final String mResultTaskId;
+        private final List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> mSourcesAndSinks;
+        private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+        HotwordDetectedResultCopyTask(String resultTaskId,
+                List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks) {
+            mResultTaskId = resultTaskId;
+            mSourcesAndSinks = sourcesAndSinks;
+        }
+
+        @Override
+        public void run() {
+            Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId);
+            int size = mSourcesAndSinks.size();
+            List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink =
+                        mSourcesAndSinks.get(i);
+                ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
+                ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
+                String streamTaskId = mResultTaskId + "@" + i;
+                tasks.add(new SingleAudioStreamCopyTask(streamTaskId, serviceAudioSource,
+                        clientAudioSink));
+            }
+
+            if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+                    mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                    mVoiceInteractorIdentity.attributionTag, OP_MESSAGE) == MODE_ALLOWED) {
+                try {
+                    // TODO(b/244599891): Set timeout, close after inactivity
+                    mExecutorService.invokeAll(tasks);
+                } catch (InterruptedException e) {
+                    Slog.e(TAG, mResultTaskId + ": Task was interrupted", e);
+                    bestEffortPropagateError(e.getMessage());
+                } finally {
+                    mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+                            mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                            mVoiceInteractorIdentity.attributionTag);
+                }
+            } else {
+                bestEffortPropagateError(
+                        "Failed to obtain RECORD_AUDIO_HOTWORD permission for "
+                                + SoundTriggerSessionPermissionsDecorator.toString(
+                                mVoiceInteractorIdentity));
+            }
+        }
+
+        private void bestEffortPropagateError(@NonNull String errorMessage) {
+            try {
+                for (Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink :
+                        mSourcesAndSinks) {
+                    ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
+                    ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
+                    serviceAudioSource.closeWithError(errorMessage);
+                    clientAudioSink.closeWithError(errorMessage);
+                }
+            } catch (IOException e) {
+                Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
+            }
+        }
+    }
+
+    private static class SingleAudioStreamCopyTask implements Callable<Void> {
+        // TODO: Make this buffer size customizable from updateState()
+        private static final int COPY_BUFFER_LENGTH = 1_024;
+
+        private final String mStreamTaskId;
+        private final ParcelFileDescriptor mAudioSource;
+        private final ParcelFileDescriptor mAudioSink;
+
+        SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
+                ParcelFileDescriptor audioSink) {
+            mStreamTaskId = streamTaskId;
+            mAudioSource = audioSource;
+            mAudioSink = audioSink;
+        }
+
+        @Override
+        public Void call() throws Exception {
+            Thread.currentThread().setName(THREAD_NAME_PREFIX + mStreamTaskId);
+
+            // Note: We are intentionally NOT using try-with-resources here. If we did,
+            // the ParcelFileDescriptors will be automatically closed WITHOUT errors before we go
+            // into the IOException-catch block. We want to propagate the error while closing the
+            // PFDs.
+            InputStream fis = null;
+            OutputStream fos = null;
+            try {
+                fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource);
+                fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink);
+                byte[] buffer = new byte[COPY_BUFFER_LENGTH];
+                while (true) {
+                    if (Thread.interrupted()) {
+                        Slog.e(TAG,
+                                mStreamTaskId + ": SingleAudioStreamCopyTask task was interrupted");
+                        break;
+                    }
+
+                    int bytesRead = fis.read(buffer);
+                    if (bytesRead < 0) {
+                        Slog.i(TAG, mStreamTaskId + ": Reached end of audio stream");
+                        break;
+                    }
+                    if (bytesRead > 0) {
+                        if (DEBUG) {
+                            // TODO(b/244599440): Add proper logging
+                            Slog.d(TAG, mStreamTaskId + ": Copied " + bytesRead
+                                    + " bytes from audio stream. First 20 bytes=" + Arrays.toString(
+                                    Arrays.copyOfRange(buffer, 0, 20)));
+                        }
+                        fos.write(buffer, 0, bytesRead);
+                    }
+                    // TODO(b/244599891): Close PFDs after inactivity
+                }
+            } catch (IOException e) {
+                mAudioSource.closeWithError(e.getMessage());
+                mAudioSink.closeWithError(e.getMessage());
+                Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+            } finally {
+                if (fis != null) {
+                    fis.close();
+                }
+                if (fos != null) {
+                    fos.close();
+                }
+            }
+
+            return null;
+        }
+    }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 372fdaf..132cffdf 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -144,6 +144,7 @@
     private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
     private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
     private static final int CALLBACK_DETECT_TIMEOUT = -3;
+    private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
 
     // Hotword metrics
     private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
@@ -175,6 +176,8 @@
     // TODO: This may need to be a Handler(looper)
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
+    private final AppOpsManager mAppOpsManager;
+    private final HotwordAudioStreamManager mHotwordAudioStreamManager;
     @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
     private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
     private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@@ -235,6 +238,9 @@
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager,
+                mVoiceInteractorIdentity);
         mDetectionComponentName = serviceName;
         mUser = userId;
         mCallback = callback;
@@ -489,13 +495,19 @@
                         return;
                     }
                     saveProximityMetersToBundle(result);
-                    mSoftwareCallback.onDetected(result, null, null);
-                    if (result != null) {
-                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
-                                + " bits from hotword trusted process");
-                        if (mDebugHotwordLogging) {
-                            Slog.i(TAG, "Egressed detected result: " + result);
-                        }
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        mSoftwareCallback.onError();
+                        return;
+                    }
+                    mSoftwareCallback.onDetected(newResult, null, null);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
                 }
             }
@@ -610,6 +622,7 @@
                         enforcePermissionsForDataDelivery();
                         enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
                     } catch (SecurityException e) {
+                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 mDetectorType,
                                 METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
@@ -617,13 +630,19 @@
                         return;
                     }
                     saveProximityMetersToBundle(result);
-                    externalCallback.onKeyphraseDetected(recognitionEvent, result);
-                    if (result != null) {
-                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
-                                + " bits from hotword trusted process");
-                        if (mDebugHotwordLogging) {
-                            Slog.i(TAG, "Egressed detected result: " + result);
-                        }
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
+                        return;
+                    }
+                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
                     }
                 }
             }
@@ -722,6 +741,7 @@
     }
 
     private void restartProcessLocked() {
+        // TODO(b/244598068): Check HotwordAudioStreamManager first
         Slog.v(TAG, "Restarting hotword detection process");
         ServiceConnection oldConnection = mRemoteHotwordDetectionService;
         HotwordDetectionServiceIdentity previousIdentity = mIdentity;
@@ -956,16 +976,24 @@
                                         callback.onError();
                                         return;
                                     }
-                                    callback.onDetected(triggerResult, null /* audioFormat */,
+                                    HotwordDetectedResult newResult;
+                                    try {
+                                        newResult =
+                                                mHotwordAudioStreamManager.startCopyingAudioStreams(
+                                                        triggerResult);
+                                    } catch (IOException e) {
+                                        // TODO: Write event
+                                        callback.onError();
+                                        return;
+                                    }
+                                    callback.onDetected(newResult, null /* audioFormat */,
                                             null /* audioStream */);
-                                    if (triggerResult != null) {
-                                        Slog.i(TAG, "Egressed "
-                                                + HotwordDetectedResult.getUsageSize(triggerResult)
-                                                + " bits from hotword trusted process");
-                                        if (mDebugHotwordLogging) {
-                                            Slog.i(TAG,
-                                                    "Egressed detected result: " + triggerResult);
-                                        }
+                                    Slog.i(TAG, "Egressed "
+                                            + HotwordDetectedResult.getUsageSize(newResult)
+                                            + " bits from hotword trusted process");
+                                    if (mDebugHotwordLogging) {
+                                        Slog.i(TAG,
+                                                "Egressed detected result: " + newResult);
                                     }
                                 }
                             });
@@ -1224,7 +1252,7 @@
         Binder.withCleanCallingIdentity(() -> {
             enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
             int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
-            mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp,
+            mAppOpsManager.noteOpNoThrow(hotwordOp,
                     mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
                     mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
             enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 151ff80..7207e373 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1174,6 +1174,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public void setDisabled(boolean disabled) {
+            super.setDisabled_enforcePermission();
+
             synchronized (this) {
                 if (mTemporarilyDisabled == disabled) {
                     if (DEBUG) Slog.d(TAG, "setDisabled(): already " + disabled);
@@ -1244,6 +1246,8 @@
         public void updateState(
                 @Nullable PersistableBundle options,
                 @Nullable SharedMemory sharedMemory) {
+            super.updateState_enforcePermission();
+
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
@@ -1260,6 +1264,8 @@
                 @Nullable SharedMemory sharedMemory,
                 IHotwordRecognitionStatusCallback callback,
                 int detectorType) {
+            super.initAndVerifyDetector_enforcePermission();
+
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
@@ -1711,6 +1717,8 @@
                 @Nullable String attributionTag,
                 @Nullable IVoiceInteractionSessionShowCallback showCallback,
                 @Nullable IBinder activityToken) {
+            super.showSessionForActiveService_enforcePermission();
+
             if (DEBUG_USER) Slog.d(TAG, "showSessionForActiveService()");
 
             synchronized (this) {
@@ -1742,6 +1750,8 @@
         @Override
         public void hideCurrentSession() throws RemoteException {
 
+            super.hideCurrentSession_enforcePermission();
+
             if (mImpl == null) {
                 return;
             }
@@ -1762,6 +1772,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public void launchVoiceAssistFromKeyguard() {
+            super.launchVoiceAssistFromKeyguard_enforcePermission();
+
             synchronized (this) {
                 if (mImpl == null) {
                     Slog.w(TAG, "launchVoiceAssistFromKeyguard without running voice interaction"
@@ -1780,6 +1792,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public boolean isSessionRunning() {
+            super.isSessionRunning_enforcePermission();
+
             synchronized (this) {
                 return mImpl != null && mImpl.mActiveSession != null;
             }
@@ -1788,6 +1802,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public boolean activeServiceSupportsAssist() {
+            super.activeServiceSupportsAssist_enforcePermission();
+
             synchronized (this) {
                 return mImpl != null && mImpl.mInfo != null && mImpl.mInfo.getSupportsAssist();
             }
@@ -1796,6 +1812,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public boolean activeServiceSupportsLaunchFromKeyguard() throws RemoteException {
+            super.activeServiceSupportsLaunchFromKeyguard_enforcePermission();
+
             synchronized (this) {
                 return mImpl != null && mImpl.mInfo != null
                         && mImpl.mInfo.getSupportsLaunchFromKeyguard();
@@ -1805,6 +1823,8 @@
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
         public void onLockscreenShown() {
+            super.onLockscreenShown_enforcePermission();
+
             synchronized (this) {
                 if (mImpl == null) {
                     return;
@@ -1828,6 +1848,8 @@
         @Override
         public void registerVoiceInteractionSessionListener(
                 IVoiceInteractionSessionListener listener) {
+            super.registerVoiceInteractionSessionListener_enforcePermission();
+
             synchronized (this) {
                 mVoiceInteractionSessionListeners.register(listener);
             }
@@ -1837,6 +1859,8 @@
         @Override
         public void getActiveServiceSupportedActions(List<String> voiceActions,
                 IVoiceActionCheckCallback callback) {
+            super.getActiveServiceSupportedActions_enforcePermission();
+
             synchronized (this) {
                 if (mImpl == null) {
                     try {
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index dc695d6..e19117b 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -730,6 +730,25 @@
     }
 
     /**
+     * Result code to string
+     *
+     * @param result The result code.
+     * @return The result code in string format.
+     *
+     * @hide
+     */
+    public static String resultToString(@Result int result) {
+        switch (result) {
+            case RESULT_OK: return "OK";
+            case RESULT_MUST_DEACTIVATE_SIM : return "MUST_DEACTIVATE_SIM";
+            case RESULT_RESOLVABLE_ERRORS: return "RESOLVABLE_ERRORS";
+            case RESULT_FIRST_USER: return "FIRST_USER";
+            default:
+            return "UNKNOWN(" + result + ")";
+        }
+    }
+
+    /**
      * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
      */
     private class IEuiccServiceWrapper extends IEuiccService.Stub {
diff --git a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
index 9add38e..46a049c 100644
--- a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
+++ b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -123,4 +123,16 @@
     public int describeContents() {
         return 0;
     }
+
+    /**
+     * @hide
+     *
+     * @return String representation of {@link GetEuiccProfileInfoListResult}
+     */
+    @Override
+    public String toString() {
+        return "[GetEuiccProfileInfoListResult: result=" + EuiccService.resultToString(result)
+                + ", isRemovable=" + mIsRemovable + ", mProfiles=" + Arrays.toString(mProfiles)
+                + "]";
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7be40b8..453c656 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8732,7 +8732,8 @@
      * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
      * returns a failure due to user action or timeout.
      * The maximum number of network boost notifications to show the user are defined in
-     * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY}.
+     * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
+     * {@link #KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT}.
      *
      * The default value is 30 minutes.
      *
@@ -8744,20 +8745,32 @@
             "premium_capability_notification_backoff_hysteresis_time_millis_long";
 
     /**
-     * The maximum number of times that we display the notification for a network boost via premium
-     * capabilities when {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * The maximum number of times in a day that we display the notification for a network boost
+     * via premium capabilities when
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
      * returns a failure due to user action or timeout.
      *
-     * An int array with 2 values: {max_notifications_per_day, max_notifications_per_month}.
-     *
-     * The default value is {2, 10}, meaning we display a maximum of 2 network boost notifications
-     * per day and 10 notifications per month.
+     * The default value is 2 times.
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
      */
-    public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY =
-            "premium_capability_maximum_notification_count_int_array";
+    public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT =
+            "premium_capability_maximum_daily_notification_count_int";
+
+    /**
+     * The maximum number of times in a month that we display the notification for a network boost
+     * via premium capabilities when
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * returns a failure due to user action or timeout.
+     *
+     * The default value is 10 times.
+     *
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+     */
+    public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT =
+            "premium_capability_maximum_monthly_notification_count_int";
 
     /**
      * The amount of time in milliseconds that the purchase request should be throttled when
@@ -8781,7 +8794,7 @@
      * During the setup time, calls to
      * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} will return
      * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
-     * If the network fails set up a slicing configuration for the premium capability within the
+     * If the network fails to set up a slicing configuration for the premium capability within the
      * setup time, subsequent purchase requests will be allowed to go through again.
      *
      * The default value is 5 minutes.
@@ -8808,7 +8821,7 @@
      * when connected to {@link TelephonyManager#NETWORK_TYPE_LTE} to purchase and use
      * premium capabilities.
      * If this is {@code false}, applications can only purchase and use premium capabilities when
-     * conencted to {@link TelephonyManager#NETWORK_TYPE_NR}.
+     * connected to {@link TelephonyManager#NETWORK_TYPE_NR}.
      *
      * This is {@code false} by default.
      */
@@ -9507,8 +9520,8 @@
                 TimeUnit.MINUTES.toMillis(30));
         sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
-        sDefaults.putIntArray(KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY,
-                new int[] {2, 10});
+        sDefaults.putInt(KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT, 2);
+        sDefaults.putInt(KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT, 10);
         sDefaults.putLong(
                 KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/DomainSelectionService.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/DomainSelectionService.aidl
index 1550ab3..b9d2ba8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/DomainSelectionService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,9 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.systemui.shared.system;
+package android.telephony;
 
-parcelable RemoteTransitionCompat;
+parcelable DomainSelectionService.SelectionAttributes;
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
new file mode 100644
index 0000000..383561a
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.Annotation.PreciseDisconnectCauses;
+import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Main domain selection implementation for various telephony features.
+ *
+ * The telephony framework will bind to the {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+public class DomainSelectionService extends Service {
+
+    private static final String LOG_TAG = "DomainSelectionService";
+
+    /**
+     * The intent that must be defined as an intent-filter in the AndroidManifest of the
+     * {@link DomainSelectionService}.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE = "android.telephony.DomainSelectionService";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SELECTOR_TYPE_",
+            value = {
+                    SELECTOR_TYPE_CALLING,
+                    SELECTOR_TYPE_SMS,
+                    SELECTOR_TYPE_UT})
+    public @interface SelectorType {}
+
+    /** Indicates the domain selector type for calling. */
+    public static final int SELECTOR_TYPE_CALLING = 1;
+    /** Indicates the domain selector type for sms. */
+    public static final int SELECTOR_TYPE_SMS = 2;
+    /** Indicates the domain selector type for supplementary services. */
+    public static final int SELECTOR_TYPE_UT = 3;
+
+    /** Indicates that the modem can scan for emergency service as per modem’s implementation. */
+    public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+    /** Indicates that the modem will scan for emergency service in limited service mode. */
+    public static final int SCAN_TYPE_LIMITED_SERVICE = 1;
+
+    /** Indicates that the modem will scan for emergency service in full service mode. */
+    public static final int SCAN_TYPE_FULL_SERVICE = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SCAN_TYPE_",
+            value = {
+                    SCAN_TYPE_NO_PREFERENCE,
+                    SCAN_TYPE_LIMITED_SERVICE,
+                    SCAN_TYPE_FULL_SERVICE})
+    public @interface EmergencyScanType {}
+
+    /**
+     * Contains attributes required to determine the domain for a telephony service.
+     */
+    public static final class SelectionAttributes implements Parcelable {
+
+        private static final String TAG = "SelectionAttributes";
+
+        private int mSlotId;
+        private int mSubId;
+        private @Nullable String mCallId;
+        private @Nullable String mNumber;
+        private @SelectorType int mSelectorType;
+        private boolean mIsVideoCall;
+        private boolean mIsEmergency;
+        private boolean mIsExitedFromAirplaneMode;
+        //private @Nullable UtAttributes mUtAttributes;
+        private @Nullable ImsReasonInfo mImsReasonInfo;
+        private @PreciseDisconnectCauses int mCause;
+        private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+        /**
+         * @param slotId The slot identifier.
+         * @param subId The subscription identifier.
+         * @param callId The call identifier.
+         * @param number The dialed number.
+         * @param selectorType Indicates the requested domain selector type.
+         * @param video Indicates it's a video call.
+         * @param emergency Indicates it's emergency service.
+         * @param exited {@code true} if the request caused the device to move out of airplane mode.
+         * @param imsReasonInfo The reason why the last PS attempt failed.
+         * @param cause The reason why the last CS attempt failed.
+         * @param regResult The current registration result for emergency services.
+         */
+        private SelectionAttributes(int slotId, int subId, @Nullable String callId,
+                @Nullable String number, @SelectorType int selectorType,
+                boolean video, boolean emergency, boolean exited,
+                /*UtAttributes attr,*/
+                @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
+                @Nullable EmergencyRegResult regResult) {
+            mSlotId = slotId;
+            mSubId = subId;
+            mCallId = callId;
+            mNumber = number;
+            mSelectorType = selectorType;
+            mIsVideoCall = video;
+            mIsEmergency = emergency;
+            mIsExitedFromAirplaneMode = exited;
+            //mUtAttributes = attr;
+            mImsReasonInfo = imsReasonInfo;
+            mCause = cause;
+            mEmergencyRegResult = regResult;
+        }
+
+        /**
+         * Copy constructor.
+         *
+         * @param s Source selection attributes.
+         * @hide
+         */
+        public SelectionAttributes(@NonNull SelectionAttributes s) {
+            mSlotId = s.mSlotId;
+            mSubId = s.mSubId;
+            mCallId = s.mCallId;
+            mNumber = s.mNumber;
+            mSelectorType = s.mSelectorType;
+            mIsEmergency = s.mIsEmergency;
+            mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
+            //mUtAttributes = s.mUtAttributes;
+            mImsReasonInfo = s.mImsReasonInfo;
+            mCause = s.mCause;
+            mEmergencyRegResult = s.mEmergencyRegResult;
+        }
+
+        /**
+         * Constructs a SelectionAttributes object from the given parcel.
+         */
+        private SelectionAttributes(@NonNull Parcel in) {
+            readFromParcel(in);
+        }
+
+        /**
+         * @return The slot identifier.
+         */
+        public int getSlotId() {
+            return mSlotId;
+        }
+
+        /**
+         * @return The subscription identifier.
+         */
+        public int getSubId() {
+            return mSubId;
+        }
+
+        /**
+         * @return The call identifier.
+         */
+        public @Nullable String getCallId() {
+            return mCallId;
+        }
+
+        /**
+         * @return The dialed number.
+         */
+        public @Nullable String getNumber() {
+            return mNumber;
+        }
+
+        /**
+         * @return The domain selector type.
+         */
+        public @SelectorType int getSelectorType() {
+            return mSelectorType;
+        }
+
+        /**
+         * @return {@code true} if the request is for a video call.
+         */
+        public boolean isVideoCall() {
+            return mIsVideoCall;
+        }
+
+        /**
+         * @return {@code true} if the request is for emergency services.
+         */
+        public boolean isEmergency() {
+            return mIsEmergency;
+        }
+
+        /**
+         * @return {@code true} if the request caused the device to move out of airplane mode.
+         */
+        public boolean isExitedFromAirplaneMode() {
+            return mIsExitedFromAirplaneMode;
+        }
+
+        /*
+        public @Nullable UtAttributes getUtAttributes();
+            return mUtAttributes;
+        }
+        */
+
+        /**
+         * @return The PS disconnect cause if trying over PS resulted in a failure and
+         *         reselection is required.
+         */
+        public @Nullable ImsReasonInfo getPsDisconnectCause() {
+            return mImsReasonInfo;
+        }
+
+        /**
+         * @return The CS disconnect cause if trying over CS resulted in a failure and
+         *         reselection is required.
+         */
+        public @PreciseDisconnectCauses int getCsDisconnectCause() {
+            return mCause;
+        }
+
+        /**
+         * @return The current registration state of cellular network.
+         */
+        public @Nullable EmergencyRegResult getEmergencyRegResult() {
+            return mEmergencyRegResult;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "{ slotId=" + mSlotId
+                    + ", subId=" + mSubId
+                    + ", callId=" + mCallId
+                    + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+                    + ", type=" + mSelectorType
+                    + ", videoCall=" + mIsVideoCall
+                    + ", emergency=" + mIsEmergency
+                    + ", airplaneMode=" + mIsExitedFromAirplaneMode
+                    + ", reasonInfo=" + mImsReasonInfo
+                    + ", cause=" + mCause
+                    + ", regResult=" + mEmergencyRegResult
+                    + " }";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            SelectionAttributes that = (SelectionAttributes) o;
+            return mSlotId == that.mSlotId && mSubId == that.mSubId
+                    && TextUtils.equals(mCallId, that.mCallId)
+                    && TextUtils.equals(mNumber, that.mNumber)
+                    && mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
+                    && mIsEmergency == that.mIsEmergency
+                    && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
+                    //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
+                    && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
+                    && mCause == that.mCause
+                    && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mCallId, mNumber, mImsReasonInfo,
+                    mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
+                    mSlotId, mSubId, mSelectorType, mCause);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeInt(mSlotId);
+            out.writeInt(mSubId);
+            out.writeString8(mCallId);
+            out.writeString8(mNumber);
+            out.writeInt(mSelectorType);
+            out.writeBoolean(mIsVideoCall);
+            out.writeBoolean(mIsEmergency);
+            out.writeBoolean(mIsExitedFromAirplaneMode);
+            //out.writeParcelable(mUtAttributes, 0);
+            out.writeParcelable(mImsReasonInfo, 0);
+            out.writeInt(mCause);
+            out.writeParcelable(mEmergencyRegResult, 0);
+        }
+
+        private void readFromParcel(@NonNull Parcel in) {
+            mSlotId = in.readInt();
+            mSubId = in.readInt();
+            mCallId = in.readString8();
+            mNumber = in.readString8();
+            mSelectorType = in.readInt();
+            mIsVideoCall = in.readBoolean();
+            mIsEmergency = in.readBoolean();
+            mIsExitedFromAirplaneMode = in.readBoolean();
+            //mUtAttributes = s.mUtAttributes;
+            mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
+                    android.telephony.ims.ImsReasonInfo.class);
+            mCause = in.readInt();
+            mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
+                    EmergencyRegResult.class);
+        }
+
+        public static final @NonNull Creator<SelectionAttributes> CREATOR =
+                new Creator<SelectionAttributes>() {
+            @Override
+            public SelectionAttributes createFromParcel(@NonNull Parcel in) {
+                return new SelectionAttributes(in);
+            }
+
+            @Override
+            public SelectionAttributes[] newArray(int size) {
+                return new SelectionAttributes[size];
+            }
+        };
+
+        private static boolean equalsHandlesNulls(Object a, Object b) {
+            return (a == null) ? (b == null) : a.equals(b);
+        }
+
+        /**
+         * Builder class creating a new instance.
+         */
+        public static final class Builder {
+            private final int mSlotId;
+            private final int mSubId;
+            private @Nullable String mCallId;
+            private @Nullable String mNumber;
+            private final @SelectorType int mSelectorType;
+            private boolean mIsVideoCall;
+            private boolean mIsEmergency;
+            private boolean mIsExitedFromAirplaneMode;
+            //private @Nullable UtAttributes mUtAttributes;
+            private @Nullable ImsReasonInfo mImsReasonInfo;
+            private @PreciseDisconnectCauses int mCause;
+            private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+            /**
+             * Default constructor for Builder.
+             */
+            public Builder(int slotId, int subId, @SelectorType int selectorType) {
+                mSlotId = slotId;
+                mSubId = subId;
+                mSelectorType = selectorType;
+            }
+
+            /**
+             * Sets the call identifier.
+             *
+             * @param callId The call identifier.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setCallId(@NonNull String callId) {
+                mCallId = callId;
+                return this;
+            }
+
+            /**
+             * Sets the dialed number.
+             *
+             * @param number The dialed number.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setNumber(@NonNull String number) {
+                mNumber = number;
+                return this;
+            }
+
+            /**
+             * Sets whether it's a video call or not.
+             *
+             * @param video Indicates it's a video call.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setVideoCall(boolean video) {
+                mIsVideoCall = video;
+                return this;
+            }
+
+            /**
+             * Sets whether it's an emergency service or not.
+             *
+             * @param emergency Indicates it's emergency service.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setEmergency(boolean emergency) {
+                mIsEmergency = emergency;
+                return this;
+            }
+
+            /**
+             * Sets whether the request caused the device to move out of airplane mode.
+             *
+             * @param exited {@code true} if the request caused the device to move out of
+             *        airplane mode.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setExitedFromAirplaneMode(boolean exited) {
+                mIsExitedFromAirplaneMode = exited;
+                return this;
+            }
+
+            /**
+             * Sets the Ut service attributes.
+             * Only applicable for SELECTOR_TYPE_UT
+             *
+             * @param attr Ut services attributes.
+             * @return The same instance of the builder.
+             */
+            /*
+            public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
+                mUtAttributes = attr;
+                return this;
+            }
+            */
+
+            /**
+             * Sets an optional reason why the last PS attempt failed.
+             *
+             * @param info The reason why the last PS attempt failed.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+                mImsReasonInfo = info;
+                return this;
+            }
+
+            /**
+             * Sets an optional reason why the last CS attempt failed.
+             *
+             * @param cause The reason why the last CS attempt failed.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setCsDisconnectCause(@PreciseDisconnectCauses int cause) {
+                mCause = cause;
+                return this;
+            }
+
+            /**
+             * Sets the current registration result for emergency services.
+             *
+             * @param regResult The current registration result for emergency services.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
+                mEmergencyRegResult = regResult;
+                return this;
+            }
+
+            /**
+             * Build the SelectionAttributes.
+             * @return The SelectionAttributes object.
+             */
+            public @NonNull SelectionAttributes build() {
+                return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
+                        mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
+                        mImsReasonInfo, mCause, mEmergencyRegResult);
+            }
+        }
+    }
+
+    /**
+     * A wrapper class for ITransportSelectorCallback interface.
+     */
+    private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+        private static final String TAG = "TransportSelectorCallbackWrapper";
+
+        private final @NonNull ITransportSelectorCallback mCallback;
+        private final @NonNull Executor mExecutor;
+
+        private @Nullable ITransportSelectorResultCallbackAdapter mResultCallback;
+        private @Nullable DomainSelectorWrapper mSelectorWrapper;
+
+        TransportSelectorCallbackWrapper(@NonNull ITransportSelectorCallback cb,
+                @NonNull Executor executor) {
+            mCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCreated(@NonNull DomainSelector selector) {
+            try {
+                mSelectorWrapper = new DomainSelectorWrapper(selector, mExecutor);
+                mCallback.onCreated(mSelectorWrapper.getCallbackBinder());
+            } catch (Exception e) {
+                Rlog.e(TAG, "onCreated e=" + e);
+            }
+        }
+
+        @Override
+        public void onWlanSelected() {
+            try {
+                mCallback.onWlanSelected();
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWlanSelected e=" + e);
+            }
+        }
+
+        @Override
+        public @NonNull WwanSelectorCallback onWwanSelected() {
+            WwanSelectorCallback callback = null;
+            try {
+                IWwanSelectorCallback cb = mCallback.onWwanSelected();
+                callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWwanSelected e=" + e);
+            }
+
+            return callback;
+        }
+
+        @Override
+        public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
+            try {
+                mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
+                mCallback.onWwanSelectedAsync(mResultCallback);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWwanSelected e=" + e);
+                executeMethodAsyncNoException(mExecutor,
+                        () -> consumer.accept(null), TAG, "onWwanSelectedAsync-Exception");
+            }
+        }
+
+        @Override
+        public void onSelectionTerminated(@DisconnectCauses int cause) {
+            try {
+                mCallback.onSelectionTerminated(cause);
+                mSelectorWrapper = null;
+            } catch (Exception e) {
+                Rlog.e(TAG, "onSelectionTerminated e=" + e);
+            }
+        }
+
+        private class ITransportSelectorResultCallbackAdapter
+                extends ITransportSelectorResultCallback.Stub {
+            private final @NonNull Consumer<WwanSelectorCallback> mConsumer;
+            private final @NonNull Executor mExecutor;
+
+            ITransportSelectorResultCallbackAdapter(
+                    @NonNull Consumer<WwanSelectorCallback> consumer,
+                    @NonNull Executor executor) {
+                mConsumer = consumer;
+                mExecutor = executor;
+            }
+
+            @Override
+            public void onCompleted(@NonNull IWwanSelectorCallback cb) {
+                if (mConsumer == null) return;
+
+                WwanSelectorCallback callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+                executeMethodAsyncNoException(mExecutor,
+                        () -> mConsumer.accept(callback), TAG, "onWwanSelectedAsync-Completed");
+            }
+        }
+    }
+
+    /**
+     * A wrapper class for IDomainSelector interface.
+     */
+    private final class DomainSelectorWrapper {
+        private static final String TAG = "DomainSelectorWrapper";
+
+        private @NonNull IDomainSelector mCallbackBinder;
+
+        DomainSelectorWrapper(@NonNull DomainSelector cb, @NonNull Executor executor) {
+            mCallbackBinder = new IDomainSelectorAdapter(cb, executor);
+        }
+
+        private class IDomainSelectorAdapter extends IDomainSelector.Stub {
+            private final @NonNull WeakReference<DomainSelector> mDomainSelectorWeakRef;
+            private final @NonNull Executor mExecutor;
+
+            IDomainSelectorAdapter(@NonNull DomainSelector domainSelector,
+                    @NonNull Executor executor) {
+                mDomainSelectorWeakRef =
+                        new WeakReference<DomainSelector>(domainSelector);
+                mExecutor = executor;
+            }
+
+            @Override
+            public void cancelSelection() {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
+            }
+
+            @Override
+            public void reselectDomain(@NonNull SelectionAttributes attr) {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.reselectDomain(attr), TAG, "reselectDomain");
+            }
+
+            @Override
+            public void finishSelection() {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.finishSelection(), TAG, "finishSelection");
+            }
+        }
+
+        public @NonNull IDomainSelector getCallbackBinder() {
+            return mCallbackBinder;
+        }
+    }
+
+    /**
+     * A wrapper class for IWwanSelectorCallback and IWwanSelectorResultCallback.
+     */
+    private final class WwanSelectorCallbackWrapper
+            implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+        private static final String TAG = "WwanSelectorCallbackWrapper";
+
+        private final @NonNull IWwanSelectorCallback mCallback;
+        private final @NonNull Executor mExecutor;
+
+        private @Nullable IWwanSelectorResultCallbackAdapter mResultCallback;
+
+        WwanSelectorCallbackWrapper(@NonNull IWwanSelectorCallback cb,
+                @NonNull Executor executor) {
+            mCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCancel() {
+            try {
+                mCallback.onCancel();
+            } catch (Exception e) {
+                Rlog.e(TAG, "onCancel e=" + e);
+            }
+        }
+
+        @Override
+        public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+                @NonNull Consumer<EmergencyRegResult> consumer) {
+            try {
+                if (signal != null) signal.setOnCancelListener(this);
+                mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
+                mCallback.onRequestEmergencyNetworkScan(
+                        preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
+                        scanType, mResultCallback);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
+            }
+        }
+
+        @Override
+        public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+            try {
+                mCallback.onDomainSelected(accessNetworkType);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onDomainSelected e=" + e);
+            }
+        }
+
+        private class IWwanSelectorResultCallbackAdapter
+                extends IWwanSelectorResultCallback.Stub {
+            private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+            private final @NonNull Executor mExecutor;
+
+            IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+                    @NonNull Executor executor) {
+                mConsumer = consumer;
+                mExecutor = executor;
+            }
+
+            @Override
+            public void onComplete(@NonNull EmergencyRegResult result) {
+                if (mConsumer == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> mConsumer.accept(result), TAG, "onScanComplete");
+            }
+        }
+    }
+
+    private final Object mExecutorLock = new Object();
+
+    /** Executor used to execute methods called remotely by the framework. */
+    private @NonNull Executor mExecutor;
+
+    /**
+     * Selects a domain for the given operation.
+     *
+     * @param attr Required to determine the domain.
+     * @param callback The callback instance being registered.
+     */
+    public void onDomainSelection(@NonNull SelectionAttributes attr,
+            @NonNull TransportSelectorCallback callback) {
+    }
+
+    /**
+     * Notifies the change in {@link ServiceState} for a specific slot.
+     *
+     * @param slotId For which the state changed.
+     * @param subId For which the state changed.
+     * @param serviceState Updated {@link ServiceState}.
+     */
+    public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+    }
+
+    /**
+     * Notifies the change in {@link BarringInfo} for a specific slot.
+     *
+     * @param slotId For which the state changed.
+     * @param subId For which the state changed.
+     * @param info Updated {@link BarringInfo}.
+     */
+    public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+    }
+
+    private final IBinder mDomainSelectionServiceController =
+            new IDomainSelectionServiceController.Stub() {
+        @Override
+        public void selectDomain(@NonNull SelectionAttributes attr,
+                @NonNull ITransportSelectorCallback callback)  throws RemoteException {
+            executeMethodAsync(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onDomainSelection(attr,
+                            new TransportSelectorCallbackWrapper(callback, getCachedExecutor())),
+                    LOG_TAG, "onDomainSelection");
+        }
+
+        @Override
+        public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+            executeMethodAsyncNoException(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
+                            subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+        }
+
+        @Override
+        public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+            executeMethodAsyncNoException(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+                    LOG_TAG, "onBarringInfoUpdated");
+        }
+    };
+
+    private static void executeMethodAsync(@NonNull Executor executor, @NonNull Runnable r,
+            @NonNull String tag, @NonNull String errorLogName) throws RemoteException {
+        try {
+            CompletableFuture.runAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+        } catch (CancellationException | CompletionException e) {
+            Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+            throw new RemoteException(e.getMessage());
+        }
+    }
+
+    private void executeMethodAsyncNoException(@NonNull Executor executor, @NonNull Runnable r,
+            @NonNull String tag, @NonNull String errorLogName) {
+        try {
+            CompletableFuture.runAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+        } catch (CancellationException | CompletionException e) {
+            Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+        }
+    }
+
+    /** @hide */
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            Log.i(LOG_TAG, "DomainSelectionService Bound.");
+            return mDomainSelectionServiceController;
+        }
+        return null;
+    }
+
+    /**
+     * The DomainSelectionService will be able to define an {@link Executor} that the service
+     * can use to execute the methods. It has set the default executor as Runnable::run,
+     *
+     * @return An {@link Executor} to be used.
+     */
+    @SuppressLint("OnNameExpected")
+    public @NonNull Executor getExecutor() {
+        return Runnable::run;
+    }
+
+    private @NonNull Executor getCachedExecutor() {
+        synchronized (mExecutorLock) {
+            if (mExecutor == null) {
+                Executor e = getExecutor();
+                mExecutor = (e != null) ? e : Runnable::run;
+            }
+            return mExecutor;
+        }
+    }
+
+    /**
+     * Returns a string representation of the domain.
+     * @param domain The domain.
+     * @return The name of the domain.
+     * @hide
+     */
+    public static @NonNull String getDomainName(@NetworkRegistrationInfo.Domain int domain) {
+        return NetworkRegistrationInfo.domainToString(domain);
+    }
+}
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
new file mode 100644
index 0000000..0871831
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+/**
+ * Implemented as part of the {@link DomainSelectionService} to implement domain selection
+ * for a specific use case.
+ * @hide
+ */
+public interface DomainSelector {
+    /**
+     * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+     * to clean up all ongoing operations with the framework.
+     */
+    void cancelSelection();
+
+    /**
+     * Reselect a domain due to the call not setting up properly.
+     *
+     * @param attr attributes required to select the domain.
+     */
+    void reselectDomain(@NonNull SelectionAttributes attr);
+
+    /**
+     * Finish the selection procedure and clean everything up.
+     */
+    void finishSelection();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/EmergencyRegResult.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/EmergencyRegResult.aidl
index 1550ab3..f722962 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/EmergencyRegResult.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -11,9 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package com.android.systemui.shared.system;
+package android.telephony;
 
-parcelable RemoteTransitionCompat;
+parcelable EmergencyRegResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
new file mode 100644
index 0000000..5aed412
--- /dev/null
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes required to determine the domain for a telephony service
+ * @hide
+ */
+public final class EmergencyRegResult implements Parcelable {
+
+    /**
+     * Indicates the cellular network type of the acquired system.
+     */
+    private @AccessNetworkConstants.RadioAccessNetworkType int mAccessNetworkType;
+
+    /**
+     * Registration state of the acquired system.
+     */
+    private @NetworkRegistrationInfo.RegistrationState int mRegState;
+
+    /**
+     * EMC domain indicates the current domain of the acquired system.
+     */
+    private @NetworkRegistrationInfo.Domain int mDomain;
+
+    /**
+     * Indicates whether the network supports voice over PS network.
+     */
+    private boolean mIsVopsSupported;
+
+    /**
+     * This indicates if camped network support VoLTE emergency bearers.
+     * This should only be set if the UE is in LTE mode.
+     */
+    private boolean mIsEmcBearerSupported;
+
+    /**
+     * The value of the network provided EMC in 5G Registration ACCEPT.
+     * This should be set only if the UE is in 5G mode.
+     */
+    private int mNwProvidedEmc;
+
+    /**
+     * The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * This should be set only if the UE is in 5G mode.
+     */
+    private int mNwProvidedEmf;
+
+    /** 3-digit Mobile Country Code, 000..999, empty string if unknown. */
+    private @NonNull String mMcc;
+
+    /** 2 or 3-digit Mobile Network Code, 00..999, empty string if unknown. */
+    private @NonNull String mMnc;
+
+    /**
+     * The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
+     * empty string if unknown.
+     */
+    private @NonNull String mIso;
+
+    /**
+     * Constructor
+     * @param accessNetwork Indicates the network type of the acquired system.
+     * @param regState Indicates the registration state of the acquired system.
+     * @param domain Indicates the current domain of the acquired system.
+     * @param isVopsSupported Indicates whether the network supports voice over PS network.
+     * @param isEmcBearerSupported  Indicates if camped network support VoLTE emergency bearers.
+     * @param emc The value of the network provided EMC in 5G Registration ACCEPT.
+     * @param emf The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * @param mcc Mobile country code, empty string if unknown.
+     * @param mnc Mobile network code, empty string if unknown.
+     * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
+     * @hide
+     */
+    public EmergencyRegResult(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+            @NetworkRegistrationInfo.RegistrationState int regState,
+            @NetworkRegistrationInfo.Domain int domain,
+            boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+            @NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
+        mAccessNetworkType = accessNetwork;
+        mRegState = regState;
+        mDomain = domain;
+        mIsVopsSupported = isVopsSupported;
+        mIsEmcBearerSupported = isEmcBearerSupported;
+        mNwProvidedEmc = emc;
+        mNwProvidedEmf = emf;
+        mMcc = mcc;
+        mMnc = mnc;
+        mIso = iso;
+    }
+
+    /**
+     * Copy constructors
+     *
+     * @param s Source emergency scan result
+     * @hide
+     */
+    public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+        mAccessNetworkType = s.mAccessNetworkType;
+        mRegState = s.mRegState;
+        mDomain = s.mDomain;
+        mIsVopsSupported = s.mIsVopsSupported;
+        mIsEmcBearerSupported = s.mIsEmcBearerSupported;
+        mNwProvidedEmc = s.mNwProvidedEmc;
+        mNwProvidedEmf = s.mNwProvidedEmf;
+        mMcc = s.mMcc;
+        mMnc = s.mMnc;
+        mIso = s.mIso;
+    }
+
+    /**
+     * Construct a EmergencyRegResult object from the given parcel.
+     */
+    private EmergencyRegResult(@NonNull Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Returns the cellular access network type of the acquired system.
+     *
+     * @return the cellular network type.
+     */
+    public @AccessNetworkConstants.RadioAccessNetworkType int getAccessNetwork() {
+        return mAccessNetworkType;
+    }
+
+    /**
+     * Returns the registration state of the acquired system.
+     *
+     * @return the registration state.
+     */
+    public @NetworkRegistrationInfo.RegistrationState int getRegState() {
+        return mRegState;
+    }
+
+    /**
+     * Returns the current domain of the acquired system.
+     *
+     * @return the current domain.
+     */
+    public @NetworkRegistrationInfo.Domain int getDomain() {
+        return mDomain;
+    }
+
+    /**
+     * Returns whether the network supports voice over PS network.
+     *
+     * @return {@code true} if the network supports voice over PS network.
+     */
+    public boolean isVopsSupported() {
+        return mIsVopsSupported;
+    }
+
+    /**
+     * Returns whether camped network support VoLTE emergency bearers.
+     * This is not valid if the UE is not in LTE mode.
+     *
+     * @return {@code true} if the network supports VoLTE emergency bearers.
+     */
+    public boolean isEmcBearerSupported() {
+        return mIsEmcBearerSupported;
+    }
+
+    /**
+     * Returns the value of the network provided EMC in 5G Registration ACCEPT.
+     * This is not valid if UE is not in 5G mode.
+     *
+     * @return the value of the network provided EMC.
+     */
+    public int getNwProvidedEmc() {
+        return mNwProvidedEmc;
+    }
+
+    /**
+     * Returns the value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * This is not valid if UE is not in 5G mode.
+     *
+     * @return the value of the network provided EMF.
+     */
+    public int getNwProvidedEmf() {
+        return mNwProvidedEmf;
+    }
+
+    /**
+     * Returns 3-digit Mobile Country Code.
+     *
+     * @return Mobile Country Code.
+     */
+    public @NonNull String getMcc() {
+        return mMcc;
+    }
+
+    /**
+     * Returns 2 or 3-digit Mobile Network Code.
+     *
+     * @return Mobile Network Code.
+     */
+    public @NonNull String getMnc() {
+        return mMnc;
+    }
+
+    /**
+     * Returns the ISO-3166-1 alpha-2 country code is provided in lowercase 2 character format.
+     *
+     * @return Country code.
+     */
+    public @NonNull String getIso() {
+        return mIso;
+    }
+
+    @Override
+    public @NonNull String toString() {
+        return "{ accessNetwork="
+                + AccessNetworkConstants.AccessNetworkType.toString(mAccessNetworkType)
+                + ", regState=" + NetworkRegistrationInfo.registrationStateToString(mRegState)
+                + ", domain=" + NetworkRegistrationInfo.domainToString(mDomain)
+                + ", vops=" + mIsVopsSupported
+                + ", emcBearer=" + mIsEmcBearerSupported
+                + ", emc=" + mNwProvidedEmc
+                + ", emf=" + mNwProvidedEmf
+                + ", mcc=" + mMcc
+                + ", mnc=" + mMnc
+                + ", iso=" + mIso
+                + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EmergencyRegResult that = (EmergencyRegResult) o;
+        return mAccessNetworkType == that.mAccessNetworkType
+                && mRegState == that.mRegState
+                && mDomain == that.mDomain
+                && mIsVopsSupported == that.mIsVopsSupported
+                && mIsEmcBearerSupported == that.mIsEmcBearerSupported
+                && mNwProvidedEmc == that.mNwProvidedEmc
+                && mNwProvidedEmf == that.mNwProvidedEmf
+                && TextUtils.equals(mMcc, that.mMcc)
+                && TextUtils.equals(mMnc, that.mMnc)
+                && TextUtils.equals(mIso, that.mIso);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAccessNetworkType, mRegState, mDomain,
+                mIsVopsSupported, mIsEmcBearerSupported,
+                mNwProvidedEmc, mNwProvidedEmf,
+                mMcc, mMnc, mIso);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mAccessNetworkType);
+        out.writeInt(mRegState);
+        out.writeInt(mDomain);
+        out.writeBoolean(mIsVopsSupported);
+        out.writeBoolean(mIsEmcBearerSupported);
+        out.writeInt(mNwProvidedEmc);
+        out.writeInt(mNwProvidedEmf);
+        out.writeString8(mMcc);
+        out.writeString8(mMnc);
+        out.writeString8(mIso);
+    }
+
+    private void readFromParcel(@NonNull Parcel in) {
+        mAccessNetworkType = in.readInt();
+        mRegState = in.readInt();
+        mDomain = in.readInt();
+        mIsVopsSupported = in.readBoolean();
+        mIsEmcBearerSupported = in.readBoolean();
+        mNwProvidedEmc = in.readInt();
+        mNwProvidedEmf = in.readInt();
+        mMcc = in.readString8();
+        mMnc = in.readString8();
+        mIso = in.readString8();
+    }
+
+    public static final @NonNull Creator<EmergencyRegResult> CREATOR =
+            new Creator<EmergencyRegResult>() {
+        @Override
+        public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
+            return new EmergencyRegResult(in);
+        }
+
+        @Override
+        public EmergencyRegResult[] newArray(int size) {
+            return new EmergencyRegResult[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index 98eeacf..d4b912e 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -66,6 +66,11 @@
     public static final int PRECISE_CALL_STATE_DISCONNECTED =   7;
     /** Call state: Disconnecting. */
     public static final int PRECISE_CALL_STATE_DISCONNECTING =  8;
+    /**
+     * Call state: Incoming in pre-alerting state.
+     * A call will be in this state prior to entering {@link #PRECISE_CALL_STATE_ALERTING}.
+     */
+    public static final int PRECISE_CALL_STATE_INCOMING_SETUP = 9;
 
     private @PreciseCallStates int mRingingCallState = PRECISE_CALL_STATE_NOT_VALID;
     private @PreciseCallStates int mForegroundCallState = PRECISE_CALL_STATE_NOT_VALID;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 35b2055..51a7840 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2975,7 +2975,11 @@
     public static final int NETWORK_TYPE_HSUPA = TelephonyProtoEnums.NETWORK_TYPE_HSUPA; // = 9.
     /** Current network is HSPA */
     public static final int NETWORK_TYPE_HSPA = TelephonyProtoEnums.NETWORK_TYPE_HSPA; // = 10.
-    /** Current network is iDen */
+    /**
+     * Current network is iDen
+     * @deprecated Legacy network type no longer being used.
+     */
+    @Deprecated
     public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11.
     /** Current network is EVDO revision B*/
     public static final int NETWORK_TYPE_EVDO_B = TelephonyProtoEnums.NETWORK_TYPE_EVDO_B; // = 12.
@@ -13983,6 +13987,7 @@
     public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
     /**
      * network type bitmask indicating the support of radio tech iDen.
+     * @hide
      */
     public static final long NETWORK_TYPE_BITMASK_IDEN = (1 << (NETWORK_TYPE_IDEN - 1));
     /**
@@ -17226,12 +17231,13 @@
      * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
      * If displaying the network boost notification is throttled, it will be for the amount of time
      * specified by {@link CarrierConfigManager
-     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_INT_ARRAY}.
+     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}.
      * If a foreground application requests premium capabilities, the network boost notification
      * will be displayed to the user regardless of the throttled status.
      * We will show the network boost notification to the user up to the daily and monthly maximum
-     * number of times specified by {@link CarrierConfigManager
-     * #KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY}.
+     * number of times specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT} and
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT}.
      * Subsequent attempts will return the same error until the request is no longer throttled
      * or throttling conditions change.
      */
@@ -17333,7 +17339,7 @@
      * Subsequent attempts will return the same error until the request is made on the default
      * data subscription.
      */
-    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14;
 
     /**
      * Purchase premium capability was successful and is waiting for the network to setup the
@@ -17341,7 +17347,7 @@
      * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG},
      * subsequent requests will return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}
      * until the purchase expires. If the setup is not complete within the time specified above,
-     * applications can reques the premium capability again.
+     * applications can request the premium capability again.
      */
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
 
@@ -17363,7 +17369,7 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
     public @interface PurchasePremiumCapabilityResult {}
 
@@ -17403,8 +17409,8 @@
                 return "NETWORK_NOT_AVAILABLE";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED:
                 return "NETWORK_CONGESTED";
-            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
-                return "NOT_DEFAULT_DATA";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB:
+                return "NOT_DEFAULT_DATA_SUB";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
                 return "PENDING_NETWORK_SETUP";
             default:
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
new file mode 100644
index 0000000..d396790
--- /dev/null
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.Annotation.DisconnectCauses;
+
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the transport selection result.
+ * @hide
+ */
+public interface TransportSelectorCallback {
+    /**
+     * Notify that {@link DomainSelector} instance has been created for the selection request.
+     *
+     * @param selector the {@link DomainSelector} instance created.
+     */
+    void onCreated(@NonNull DomainSelector selector);
+
+    /**
+     * Notify that WLAN transport has been selected.
+     */
+    void onWlanSelected();
+
+    /**
+     * Notify that WWAN transport has been selected.
+     */
+    @NonNull WwanSelectorCallback onWwanSelected();
+
+    /**
+     * Notify that WWAN transport has been selected.
+     *
+     * @param consumer The callback to receive the result.
+     */
+    void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+
+    /**
+     * Notify that selection has terminated because there is no decision that can be made
+     * or a timeout has occurred. The call should be terminated when this method is called.
+     *
+     * @param cause indicates the reason.
+     */
+    void onSelectionTerminated(@DisconnectCauses int cause);
+}
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
new file mode 100644
index 0000000..489a589
--- /dev/null
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.CancellationSignal;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the domain selection result.
+ * @hide
+ */
+public interface WwanSelectorCallback {
+    /**
+     * Notify the framework that the {@link DomainSelectionService} has requested an emergency
+     * network scan as part of selection.
+     *
+     * @param preferredNetworks the ordered list of preferred networks to scan.
+     * @param scanType indicates the scan preference, such as full service or limited service.
+     * @param signal notifies when the operation is canceled.
+     * @param consumer the handler of the response.
+     */
+    void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+            @EmergencyScanType int scanType,
+            @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+
+    /**
+     * Notifies the FW that the domain has been selected. After this method is called,
+     * this interface can be discarded.
+     *
+     * @param accessNetworkType the selected network type.
+     */
+    void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 0a2bb3d..e61d1e6 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -118,7 +118,10 @@
     /** Resets the default SM-DP+ address. */
     public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 1 << 2;
 
-    /** Result code when the requested profile is not found */
+    /** Result code when the requested profile is not found
+     * @deprecated use {@link #RESULT_PROFILE_DOES_NOT_EXIST}
+     **/
+    @Deprecated
     public static final int RESULT_PROFILE_NOT_FOUND = 1;
 
     /** Result code of execution with no error. */
@@ -133,6 +136,9 @@
     /** Result code indicating the caller is not the active LPA. */
     public static final int RESULT_CALLER_NOT_ALLOWED = -3;
 
+    /** Result code when the requested profile does not exist */
+    public static final int RESULT_PROFILE_DOES_NOT_EXIST = -4;
+
     /**
      * Callback to receive the result of an eUICC card API.
      *
@@ -224,7 +230,7 @@
 
     /**
      * Requests the enabled profile for a given port on an eUicc. Callback with result code
-     * {@link RESULT_PROFILE_NOT_FOUND} and {@code NULL} EuiccProfile if there is no enabled
+     * {@link RESULT_PROFILE_DOES_NOT_EXIST} and {@code NULL} EuiccProfile if there is no enabled
      * profile on the target port.
      *
      * @param cardId    The Id of the eUICC.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/ims/SrvccCall.aidl
similarity index 80%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
rename to telephony/java/android/telephony/ims/SrvccCall.aidl
index 1550ab3..0f0a079 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/ims/SrvccCall.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (c) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.telephony.ims;
 
-parcelable RemoteTransitionCompat;
+parcelable SrvccCall;
diff --git a/telephony/java/android/telephony/ims/SrvccCall.java b/telephony/java/android/telephony/ims/SrvccCall.java
new file mode 100644
index 0000000..cdc271e
--- /dev/null
+++ b/telephony/java/android/telephony/ims/SrvccCall.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.PreciseCallStates;
+
+import java.util.Objects;
+
+/**
+ * A Parcelable object to represent the current state of an IMS call that is being tracked
+ * in the ImsService when an SRVCC begins. This information will be delivered to modem.
+ * @see SrvccStartedCallback
+ *
+ * @hide
+ */
+@SystemApi
+public final class SrvccCall implements Parcelable {
+    private static final String TAG = "SrvccCall";
+
+    /** The IMS call profile */
+    private ImsCallProfile mImsCallProfile;
+
+    /** The IMS call id */
+    private String mCallId;
+
+    /** The call state */
+    private @PreciseCallStates int mCallState;
+
+    private SrvccCall(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Constructs an instance of SrvccCall.
+     *
+     * @param callId the call ID associated with the IMS call
+     * @param callState the state of this IMS call
+     * @param imsCallProfile the profile associated with this IMS call
+     * @throws IllegalArgumentException if the callId or the imsCallProfile is null
+     */
+    public SrvccCall(@NonNull String callId, @PreciseCallStates int callState,
+            @NonNull ImsCallProfile imsCallProfile) {
+        if (callId == null) throw new IllegalArgumentException("callId is null");
+        if (imsCallProfile == null) throw new IllegalArgumentException("imsCallProfile is null");
+
+        mCallId = callId;
+        mCallState = callState;
+        mImsCallProfile = imsCallProfile;
+    }
+
+    /**
+     * @return the {@link ImsCallProfile} associated with this IMS call,
+     * which will be used to get the address, the name, and the audio direction
+     * including the call in pre-alerting state.
+     */
+    @NonNull
+    public ImsCallProfile getImsCallProfile() {
+        return mImsCallProfile;
+    }
+
+    /**
+     * @return the call ID associated with this IMS call.
+     *
+     * @see android.telephony.ims.stub.ImsCallSessionImplBase#getCallId().
+     */
+    @NonNull
+    public String getCallId() {
+        return mCallId;
+    }
+
+    /**
+     * @return the call state of the associated IMS call.
+     */
+    public @PreciseCallStates int getPreciseCallState() {
+        return mCallState;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "{ callId=" + mCallId
+                + ", callState=" + mCallState
+                + ", imsCallProfile=" + mImsCallProfile
+                + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SrvccCall that = (SrvccCall) o;
+        return mImsCallProfile.equals(that.mImsCallProfile)
+                && mCallId.equals(that.mCallId)
+                && mCallState == that.mCallState;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mImsCallProfile, mCallId);
+        result = 31 * result + mCallState;
+        return result;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mCallId);
+        out.writeInt(mCallState);
+        out.writeParcelable(mImsCallProfile, 0);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mCallId = in.readString();
+        mCallState = in.readInt();
+        mImsCallProfile = in.readParcelable(ImsCallProfile.class.getClassLoader(),
+                android.telephony.ims.ImsCallProfile.class);
+    }
+
+    public static final @android.annotation.NonNull Creator<SrvccCall> CREATOR =
+            new Creator<SrvccCall>() {
+        @Override
+        public SrvccCall createFromParcel(Parcel in) {
+            return new SrvccCall(in);
+        }
+
+        @Override
+        public SrvccCall[] newArray(int size) {
+            return new SrvccCall[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
index 801b81c..8519173 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
@@ -20,6 +20,7 @@
 import android.telephony.ims.aidl.IImsMmTelListener;
 import android.telephony.ims.aidl.IImsSmsListener;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.RtpHeaderExtensionType;
 
@@ -55,6 +56,10 @@
             IImsCapabilityCallback c);
     oneway void queryCapabilityConfiguration(int capability, int radioTech,
             IImsCapabilityCallback c);
+    oneway void notifySrvccStarted(in ISrvccStartedCallback cb);
+    oneway void notifySrvccCompleted();
+    oneway void notifySrvccFailed();
+    oneway void notifySrvccCanceled();
     // SMS APIs
     void setSmsListener(IImsSmsListener l);
     oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/android/telephony/ims/aidl/ISrvccStartedCallback.aidl
similarity index 67%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/android/telephony/ims/aidl/ISrvccStartedCallback.aidl
index f79ca10..a173abf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/android/telephony/ims/aidl/ISrvccStartedCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (c) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package android.telephony.ims.aidl;
 
-import android.content.Intent;
+import android.telephony.ims.SrvccCall;
+
+import java.util.List;
 
 /**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * {@hide}
  */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
+oneway interface ISrvccStartedCallback {
+    void onSrvccCallNotified(in List<SrvccCall> profiles);
 }
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index c0ff12e..8147759 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -31,10 +31,12 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.SrvccCall;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.aidl.IImsMmTelListener;
 import android.telephony.ims.aidl.IImsSmsListener;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.stub.ImsCallSessionImplBase;
 import android.telephony.ims.stub.ImsEcbmImplBase;
 import android.telephony.ims.stub.ImsMultiEndpointImplBase;
@@ -60,6 +62,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -289,6 +292,38 @@
                     "onSmsReady");
         }
 
+        @Override
+        public void notifySrvccStarted(final ISrvccStartedCallback cb) {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccStarted(
+                            (profiles) -> {
+                                try {
+                                    cb.onSrvccCallNotified(profiles);
+                                } catch (Exception e) {
+                                    Log.e(LOG_TAG, "onSrvccCallNotified e=" + e);
+                                }
+                            }),
+                    "notifySrvccStarted");
+        }
+
+        @Override
+        public void notifySrvccCompleted() {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccCompleted(), "notifySrvccCompleted");
+        }
+
+        @Override
+        public void notifySrvccFailed() {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccFailed(), "notifySrvccFailed");
+        }
+
+        @Override
+        public void notifySrvccCanceled() {
+            executeMethodAsyncNoException(
+                    () -> MmTelFeature.this.notifySrvccCanceled(), "notifySrvccCanceled");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -969,6 +1004,75 @@
                 "Not implemented on device.");
     }
 
+    /**
+     * Notifies the MmTelFeature that the network has initiated an SRVCC (Single radio voice
+     * call continuity) for all IMS calls. When the network initiates an SRVCC, calls from
+     * the LTE domain are handed over to the legacy circuit switched domain. The modem requires
+     * knowledge of ongoing calls in the IMS domain in order to complete the SRVCC operation.
+     * <p>
+     * @param consumer The callback used to notify the framework of the list of IMS calls and their
+     * state at the time of the SRVCC.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccStarted(@NonNull Consumer<List<SrvccCall>> consumer) {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
+    /**
+     * Notifies the MmTelFeature that the SRVCC is completed and the calls have been moved
+     * over to the circuit-switched domain.
+     * {@link android.telephony.CarrierConfigManager.ImsVoice#KEY_SRVCC_TYPE_INT_ARRAY}
+     * specifies the calls can be moved. Other calls will be disconnected.
+     * <p>
+     * The MmTelFeature may now release all resources related to the IMS calls.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccCompleted() {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
+    /**
+     * Notifies the MmTelFeature that the SRVCC has failed.
+     *
+     * The handover can fail by encountering a failure at the radio level
+     * or temporary MSC server internal errors in handover procedure.
+     * Refer to 3GPP TS 23.216 section 8 Handover Failure.
+     * <p>
+     * IMS service will recover and continue calls over IMS.
+     * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text
+     * set to "failure to transition to CS domain".
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccFailed() {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
+    /**
+     * Notifies the MmTelFeature that the SRVCC has been canceled.
+     *
+     * Since the state of network can be changed, the network can decide to terminate
+     * the handover procedure before its completion and to return to its state before the handover
+     * procedure was triggered.
+     * Refer to 3GPP TS 23.216 section 8.1.3 Handover Cancellation.
+     *
+     * <p>
+     * IMS service will recover and continue calls over IMS.
+     * Per TS 24.237 12.2.4.2, UE shall send SIP UPDATE request containing the reason-text
+     * set to "handover canceled".
+     *
+     * @hide
+     */
+    @SystemApi
+    public void notifySrvccCanceled() {
+        // Base Implementation - Should be overridden by IMS service
+    }
+
     private void setSmsListener(IImsSmsListener listener) {
         getSmsImplementation().registerSmsListener(listener);
     }
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
new file mode 100644
index 0000000..8ad79ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.BarringInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+
+oneway interface IDomainSelectionServiceController {
+    void selectDomain(in SelectionAttributes attr, in ITransportSelectorCallback callback);
+    void updateServiceState(int slotId, int subId, in ServiceState serviceState);
+    void updateBarringInfo(int slotId, int subId, in BarringInfo info);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
similarity index 70%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index f79ca10..d94840b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
 
-import android.content.Intent;
+import android.telephony.DomainSelectionService.SelectionAttributes;
 
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
+oneway interface IDomainSelector {
+    void cancelSelection();
+    void reselectDomain(in SelectionAttributes attr);
+    void finishSelection();
 }
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
new file mode 100644
index 0000000..aca83f4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+interface ITransportSelectorCallback {
+    oneway void onCreated(in IDomainSelector selector);
+    oneway void onWlanSelected();
+    IWwanSelectorCallback onWwanSelected();
+    oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+    oneway void onSelectionTerminated(int cause);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
similarity index 72%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
rename to telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
index f79ca10..1460b45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
 
-import android.content.Intent;
+import com.android.internal.telephony.IWwanSelectorCallback;
 
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
+oneway interface ITransportSelectorResultCallback {
+    void onCompleted(in IWwanSelectorCallback cb);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
similarity index 63%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index f79ca10..65d994b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
 
-import android.content.Intent;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
 
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
+oneway interface IWwanSelectorCallback {
+    void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
+            int scanType, in IWwanSelectorResultCallback cb);
+    void onDomainSelected(int accessNetworkType);
+    void onCancel();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
similarity index 72%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index f79ca10..0d61fcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
 
-import android.content.Intent;
+import android.telephony.EmergencyRegResult;
 
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
-    void showTask(in Intent intent) = 1;
-
+oneway interface IWwanSelectorResultCallback {
+    void onComplete(in EmergencyRegResult result);
 }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b2f7be6..0f83a05 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -239,4 +239,10 @@
     public static final int CELL_OFF_FLAG = 0;
     public static final int CELL_ON_FLAG = 1;
     public static final int CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG = 2;
+
+    /** The key to specify the selected domain for dialing calls. */
+    public static final String EXTRA_DIAL_DOMAIN = "dial_domain";
+
+    /** The key to specify the emergency service category */
+    public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
 }
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 29d87a2..ec1709c 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -25,6 +25,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -98,6 +99,7 @@
     }
 
     @Test
+    @Ignore
     public void testBadUpdateRollback() throws Exception {
         // Need to switch user in order to send broadcasts in device tests
         assertTrue(getDevice().switchUser(mSecondaryUserId));
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index cbe13d9..650686f 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -373,6 +373,10 @@
         try (InputStream is = new FileInputStream(certPath)) {
             result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
         }
+        // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
+        final String copiedCert = "/data/fonts/debug_cert.der";
+        runShellCommand("cp " + certPath + " " + copiedCert, null);
+        runShellCommand("cmd font install-debug-cert " + copiedCert, null);
         // Assert that there are no errors.
         assertThat(result.second).isEmpty();
         String keyId = result.first.trim();
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index df09e47..d0850b8 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -69,7 +69,9 @@
     StringPiece attr_value = attr->value;
     const char* startChar = attr_value.begin();
     if (attr_name == "pathPattern") {
-      if (*startChar == '/' || *startChar == '.' || *startChar == '*') {
+      // pathPattern starts with '.' or '*' does not need leading slash.
+      // Reference starts with @ does not need leading slash.
+      if (*startChar == '/' || *startChar == '.' || *startChar == '*' || *startChar == '@') {
         return true;
       } else {
         diag->Error(android::DiagMessage(data_el->line_number)
@@ -80,7 +82,8 @@
         return false;
       }
     } else {
-      if (*startChar == '/') {
+      // Reference starts with @ does not need leading slash.
+      if (*startChar == '/' || *startChar == '@') {
         return true;
       } else {
         diag->Error(android::DiagMessage(data_el->line_number)
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index cec9a1a..8d1a647 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -1408,5 +1408,24 @@
       </application>
     </manifest>)";
   EXPECT_THAT(Verify(input), NotNull());
+
+  // DeepLink with string reference as a path.
+  input = R"(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android">
+      <application>
+        <activity android:name=".MainActivity">
+          <intent-filter>
+            <action android:name="android.intent.action.VIEW" />
+            <category android:name="android.intent.category.DEFAULT" />
+            <category android:name="android.intent.category.BROWSABLE" />
+            <data android:scheme="http"
+                          android:host="www.example.com"
+                          android:path="@string/startup_uri" />
+          </intent-filter>
+        </activity>
+      </application>
+    </manifest>)";
+  EXPECT_THAT(Verify(input), NotNull());
 }
 }  // namespace aapt
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 35a0ce6..87b4c68 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -381,62 +381,14 @@
         return tuple(f"{s:X}" for s in sequence)
     return hex(sequence)
 
-def check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji):
-    # A PUA should point to every RGI emoji and that PUA should be unique to the
-    # set of equivalent sequences for the emoji.
-    problems = []
-    for seq in all_emoji:
-        # We're looking to match not-PUA with PUA so filter out existing PUA
-        if contains_pua(seq):
-            continue
-
-        # Filter out non-RGI things that end up in all_emoji
-        if only_tags(seq) or seq in {ZWJ, COMBINING_KEYCAP, EMPTY_FLAG_SEQUENCE}:
-            continue
-
-        equivalents = [seq]
-        if seq in equivalent_emoji:
-            equivalents.append(equivalent_emoji[seq])
-
-        # If there are problems the hex code is much more useful
-        log_equivalents = [hex_strs(s) for s in equivalents]
-
-        # The system compat font should NOT include regional indicators as these have been split out
-        if contains_regional_indicator(seq):
-            assert not any(s in coverage for s in equivalents), f"Regional indicators not expected in compat font, found {log_equivalents}"
-            continue
-
-        glyph = {coverage[e] for e in equivalents}
-        if len(glyph) != 1:
-            problems.append(f"{log_equivalents} should all point to the same glyph")
-            continue
-        glyph = next(iter(glyph))
-
-        pua = {s for s, g in coverage.items() if contains_pua(s) and g == glyph}
-        if not pua:
-            problems.append(f"Expected PUA for {log_equivalents} but none exist")
-            continue
-
-    assert not problems, "\n".join(sorted(problems)) + f"\n{len(problems)} PUA problems"
-
-def check_emoji_compat(all_emoji, equivalent_emoji):
+def check_emoji_not_compat(all_emoji, equivalent_emoji):
     compat_psnames = set()
     for emoji_font in get_emoji_fonts():
         ttf = open_font(emoji_font)
         psname = get_psname(ttf)
 
-        is_compat_font = "meta" in ttf and 'Emji' in ttf["meta"].data
-        if not is_compat_font:
-            continue
-        compat_psnames.add(psname)
-
-        # If the font has compat metadata it should have PUAs for emoji sequences
-        coverage = get_emoji_map(emoji_font)
-        check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji)
-
-
-    # NotoColorEmoji must be a Compat font.
-    assert 'NotoColorEmoji' in compat_psnames, 'NotoColorEmoji MUST be a compat font'
+        if "meta" in ttf:
+            assert 'Emji' not in ttf["meta"].data, 'NotoColorEmoji MUST be a compat font'
 
 
 def check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji):
@@ -847,7 +799,7 @@
         ucd_path = sys.argv[3]
         parse_ucd(ucd_path)
         all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji()
-        check_emoji_compat(all_emoji, equivalent_emoji)
+        check_emoji_not_compat(all_emoji, equivalent_emoji)
         check_emoji_coverage(all_emoji, equivalent_emoji)
         check_emoji_defaults(default_emoji)
 
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index 6efd1f6..ff1f8e2 100644
--- a/tools/locked_region_code_injection/Android.bp
+++ b/tools/locked_region_code_injection/Android.bp
@@ -16,6 +16,6 @@
         "asm-commons-9.2",
         "asm-tree-9.2",
         "asm-analysis-9.2",
-        "guava-21.0",
+        "guava",
     ],
 }
diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp
index 39d1b1c..bb32df6 100644
--- a/tools/traceinjection/Android.bp
+++ b/tools/traceinjection/Android.bp
@@ -16,7 +16,7 @@
         "asm-commons-9.2",
         "asm-tree-9.2",
         "asm-analysis-9.2",
-        "guava-21.0",
+        "guava",
     ],
 }